diff --git a/src/export/pdf/mod.rs b/src/export/pdf/mod.rs index 2f6f255d8..5f47043ee 100644 --- a/src/export/pdf/mod.rs +++ b/src/export/pdf/mod.rs @@ -16,7 +16,7 @@ use self::outline::{Heading, HeadingNode}; use self::page::Page; use crate::font::Font; use crate::frame::{Frame, Lang}; -use crate::geom::{Dir, Em, Length}; +use crate::geom::{Abs, Dir, Em}; use crate::image::Image; /// Export a collection of frames into a PDF file. @@ -182,13 +182,13 @@ where } } -/// Additional methods for [`Length`]. -trait LengthExt { - /// Convert an em length to a number of points. +/// Additional methods for [`Abs`]. +trait AbsExt { + /// Convert an to a number of points. fn to_f32(self) -> f32; } -impl LengthExt for Length { +impl AbsExt for Abs { fn to_f32(self) -> f32 { self.to_pt() as f32 } diff --git a/src/export/pdf/outline.rs b/src/export/pdf/outline.rs index f5530f86a..73dd9e96e 100644 --- a/src/export/pdf/outline.rs +++ b/src/export/pdf/outline.rs @@ -1,7 +1,7 @@ use pdf_writer::{Finish, Ref, TextStr}; -use super::{LengthExt, PdfContext, RefExt}; -use crate::geom::{Length, Point}; +use super::{AbsExt, PdfContext, RefExt}; +use crate::geom::{Abs, Point}; use crate::util::EcoString; /// A heading that can later be linked in the outline panel. @@ -77,7 +77,7 @@ pub fn write_outline_item( outline.title(TextStr(&node.heading.content)); outline.dest_direct().page(node.heading.page).xyz( node.heading.position.x.to_f32(), - (node.heading.position.y + Length::pt(3.0)).to_f32(), + (node.heading.position.y + Abs::pt(3.0)).to_f32(), None, ); diff --git a/src/export/pdf/page.rs b/src/export/pdf/page.rs index 1b1bec927..7fbf78338 100644 --- a/src/export/pdf/page.rs +++ b/src/export/pdf/page.rs @@ -3,12 +3,12 @@ use pdf_writer::writers::ColorSpace; use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str}; use super::{ - deflate, EmExt, Heading, HeadingNode, LengthExt, PdfContext, RefExt, D65_GRAY, SRGB, + deflate, AbsExt, EmExt, Heading, HeadingNode, PdfContext, RefExt, D65_GRAY, SRGB, }; use crate::font::Font; use crate::frame::{Destination, Element, Frame, Group, Role, Text}; use crate::geom::{ - self, Color, Em, Geometry, Length, Numeric, Paint, Point, Ratio, Shape, Size, Stroke, + self, Abs, Color, Em, Geometry, Numeric, Paint, Point, Ratio, Shape, Size, Stroke, Transform, }; use crate::image::Image; @@ -45,7 +45,7 @@ pub fn construct_page(ctx: &mut PdfContext, frame: &Frame) { ky: Ratio::zero(), kx: Ratio::zero(), sy: Ratio::new(-1.0), - tx: Length::zero(), + tx: Abs::zero(), ty: size.y, }); @@ -169,7 +169,7 @@ struct PageContext<'a> { #[derive(Debug, Default, Clone)] struct State { transform: Transform, - font: Option<(Font, Length)>, + font: Option<(Font, Abs)>, fill: Option, fill_space: Option>, stroke: Option, @@ -200,7 +200,7 @@ impl<'a> PageContext<'a> { ]); } - fn set_font(&mut self, font: &Font, size: Length) { + fn set_font(&mut self, font: &Font, size: Abs) { if self.state.font.as_ref().map(|(f, s)| (f, *s)) != Some((font, size)) { self.parent.font_map.insert(font.clone()); let name = format_eco!("F{}", self.parent.font_map.map(font.clone())); @@ -402,6 +402,12 @@ fn write_shape(ctx: &mut PageContext, x: f32, y: f32, shape: &Shape) { } match shape.geometry { + Geometry::Line(target) => { + let dx = target.x.to_f32(); + let dy = target.y.to_f32(); + ctx.content.move_to(x, y); + ctx.content.line_to(x + dx, y + dy); + } Geometry::Rect(size) => { let w = size.x.to_f32(); let h = size.y.to_f32(); @@ -409,16 +415,6 @@ fn write_shape(ctx: &mut PageContext, x: f32, y: f32, shape: &Shape) { ctx.content.rect(x, y, w, h); } } - Geometry::Ellipse(size) => { - let approx = geom::Path::ellipse(size); - write_path(ctx, x, y, &approx); - } - Geometry::Line(target) => { - let dx = target.x.to_f32(); - let dy = target.y.to_f32(); - ctx.content.move_to(x, y); - ctx.content.line_to(x + dx, y + dy); - } Geometry::Path(ref path) => { write_path(ctx, x, y, path); } @@ -469,10 +465,10 @@ fn write_image(ctx: &mut PageContext, x: f32, y: f32, image: &Image, size: Size) /// Save a link for later writing in the annotations dictionary. fn write_link(ctx: &mut PageContext, pos: Point, dest: &Destination, size: Size) { - let mut min_x = Length::inf(); - let mut min_y = Length::inf(); - let mut max_x = -Length::inf(); - let mut max_y = -Length::inf(); + let mut min_x = Abs::inf(); + let mut min_y = Abs::inf(); + let mut max_x = -Abs::inf(); + let mut max_y = -Abs::inf(); // Compute the bounding box of the transformed link. for point in [ diff --git a/src/export/render.rs b/src/export/render.rs index e8702f34f..df8f512f2 100644 --- a/src/export/render.rs +++ b/src/export/render.rs @@ -10,7 +10,7 @@ use usvg::FitTo; use crate::frame::{Element, Frame, Group, Text}; use crate::geom::{ - self, Geometry, Length, Paint, PathElement, Shape, Size, Stroke, Transform, + self, Abs, Geometry, Paint, PathElement, Shape, Size, Stroke, Transform, }; use crate::image::{DecodedImage, Image}; @@ -286,18 +286,17 @@ fn render_shape( shape: &Shape, ) -> Option<()> { let path = match shape.geometry { + Geometry::Line(target) => { + let mut builder = sk::PathBuilder::new(); + builder.line_to(target.x.to_f32(), target.y.to_f32()); + builder.finish()? + } Geometry::Rect(size) => { let w = size.x.to_f32(); let h = size.y.to_f32(); let rect = sk::Rect::from_xywh(0.0, 0.0, w, h)?; sk::PathBuilder::from_rect(rect) } - Geometry::Ellipse(size) => convert_path(&geom::Path::ellipse(size))?, - Geometry::Line(target) => { - let mut builder = sk::PathBuilder::new(); - builder.line_to(target.x.to_f32(), target.y.to_f32()); - builder.finish()? - } Geometry::Path(ref path) => convert_path(path)?, }; @@ -460,12 +459,12 @@ impl OutlineBuilder for WrappedPathBuilder { } /// Additional methods for [`Length`]. -trait LengthExt { - /// Convert an em length to a number of points as f32. +trait AbsExt { + /// Convert to a number of points as f32. fn to_f32(self) -> f32; } -impl LengthExt for Length { +impl AbsExt for Abs { fn to_f32(self) -> f32 { self.to_pt() as f32 } diff --git a/src/frame.rs b/src/frame.rs index a367935c4..e85256f52 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use crate::font::Font; use crate::geom::{ - Align, Dir, Em, Length, Numeric, Paint, Point, Shape, Size, Spec, Transform, + Abs, Align, Axes, Dir, Em, Numeric, Paint, Point, Shape, Size, Transform, }; use crate::image::Image; use crate::model::{Dict, Value}; @@ -19,7 +19,7 @@ pub struct Frame { size: Size, /// The baseline of the frame measured from the top. If this is `None`, the /// frame's implicit baseline is at the bottom. - baseline: Option, + baseline: Option, /// The semantic role of the frame. role: Option, /// The elements composing this layout. @@ -58,22 +58,22 @@ impl Frame { } /// The width of the frame. - pub fn width(&self) -> Length { + pub fn width(&self) -> Abs { self.size.x } /// The height of the frame. - pub fn height(&self) -> Length { + pub fn height(&self) -> Abs { self.size.y } /// The baseline of the frame. - pub fn baseline(&self) -> Length { + pub fn baseline(&self) -> Abs { self.baseline.unwrap_or(self.size.y) } /// Set the frame's baseline from the top. - pub fn set_baseline(&mut self, baseline: Length) { + pub fn set_baseline(&mut self, baseline: Abs) { self.baseline = Some(baseline); } @@ -221,7 +221,7 @@ impl Frame { /// Resize the frame to a new size, distributing new space according to the /// given alignments. - pub fn resize(&mut self, target: Size, aligns: Spec) { + pub fn resize(&mut self, target: Size, aligns: Axes) { if self.size != target { let offset = Point::new( aligns.x.position(target.x - self.size.x), @@ -354,7 +354,7 @@ pub struct Text { /// The font the glyphs are contained in. pub font: Font, /// The font size. - pub size: Length, + pub size: Abs, /// Glyph color. pub fill: Paint, /// The natural language of the text. @@ -365,7 +365,7 @@ pub struct Text { impl Text { /// The width of the text run. - pub fn width(&self) -> Length { + pub fn width(&self) -> Abs { self.glyphs.iter().map(|g| g.x_advance).sum::().at(self.size) } } diff --git a/src/geom/abs.rs b/src/geom/abs.rs new file mode 100644 index 000000000..4429e46dd --- /dev/null +++ b/src/geom/abs.rs @@ -0,0 +1,261 @@ +use super::*; + +/// An absolute length. +#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Abs(Scalar); + +impl Abs { + /// The zero length. + pub const fn zero() -> Self { + Self(Scalar(0.0)) + } + + /// The infinite length. + pub const fn inf() -> Self { + Self(Scalar(f64::INFINITY)) + } + + /// Create an absolute length from a number of raw units. + pub const fn raw(raw: f64) -> Self { + Self(Scalar(raw)) + } + + /// Create an absolute length from a value in a unit. + pub fn with_unit(val: f64, unit: AbsUnit) -> Self { + Self(Scalar(val * unit.raw_scale())) + } + + /// Create an absolute length from a number of points. + pub fn pt(pt: f64) -> Self { + Self::with_unit(pt, AbsUnit::Pt) + } + + /// Create an absolute length from a number of millimeters. + pub fn mm(mm: f64) -> Self { + Self::with_unit(mm, AbsUnit::Mm) + } + + /// Create an absolute length from a number of centimeters. + pub fn cm(cm: f64) -> Self { + Self::with_unit(cm, AbsUnit::Cm) + } + + /// Create an absolute length from a number of inches. + pub fn inches(inches: f64) -> Self { + Self::with_unit(inches, AbsUnit::In) + } + + /// Get the value of this absolute length in raw units. + pub const fn to_raw(self) -> f64 { + (self.0).0 + } + + /// Get the value of this absolute length in a unit. + pub fn to_unit(self, unit: AbsUnit) -> f64 { + self.to_raw() / unit.raw_scale() + } + + /// Convert this to a number of points. + pub fn to_pt(self) -> f64 { + self.to_unit(AbsUnit::Pt) + } + + /// Convert this to a number of millimeters. + pub fn to_mm(self) -> f64 { + self.to_unit(AbsUnit::Mm) + } + + /// Convert this to a number of centimeters. + pub fn to_cm(self) -> f64 { + self.to_unit(AbsUnit::Cm) + } + + /// Convert this to a number of inches. + pub fn to_inches(self) -> f64 { + self.to_unit(AbsUnit::In) + } + + /// The absolute value of this length. + pub fn abs(self) -> Self { + Self::raw(self.to_raw().abs()) + } + + /// The minimum of this and another absolute length. + pub fn min(self, other: Self) -> Self { + Self(self.0.min(other.0)) + } + + /// Set to the minimum of this and another absolute length. + pub fn set_min(&mut self, other: Self) { + *self = (*self).min(other); + } + + /// The maximum of this and another absolute length. + pub fn max(self, other: Self) -> Self { + Self(self.0.max(other.0)) + } + + /// Set to the maximum of this and another absolute length. + pub fn set_max(&mut self, other: Self) { + *self = (*self).max(other); + } + + /// Whether the other absolute length fits into this one (i.e. is smaller). + /// Allows for a bit of slack. + pub fn fits(self, other: Self) -> bool { + self.0 + 1e-6 >= other.0 + } + + /// Compares two absolute lengths for whether they are approximately equal. + pub fn approx_eq(self, other: Self) -> bool { + self == other || (self - other).to_raw().abs() < 1e-6 + } + + /// Perform a checked division by a number, returning zero if the result + /// is not finite. + pub fn safe_div(self, number: f64) -> Self { + let result = self.to_raw() / number; + if result.is_finite() { + Self::raw(result) + } else { + Self::zero() + } + } +} + +impl Numeric for Abs { + fn zero() -> Self { + Self::zero() + } + + fn is_finite(self) -> bool { + self.0.is_finite() + } +} + +impl Debug for Abs { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}pt", round_2(self.to_pt())) + } +} + +impl Neg for Abs { + type Output = Self; + + fn neg(self) -> Self { + Self(-self.0) + } +} + +impl Add for Abs { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self(self.0 + other.0) + } +} + +sub_impl!(Abs - Abs -> Abs); + +impl Mul for Abs { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self(self.0 * other) + } +} + +impl Mul for f64 { + type Output = Abs; + + fn mul(self, other: Abs) -> Abs { + other * self + } +} + +impl Div for Abs { + type Output = Self; + + fn div(self, other: f64) -> Self { + Self(self.0 / other) + } +} + +impl Div for Abs { + type Output = f64; + + fn div(self, other: Self) -> f64 { + self.to_raw() / other.to_raw() + } +} + +assign_impl!(Abs += Abs); +assign_impl!(Abs -= Abs); +assign_impl!(Abs *= f64); +assign_impl!(Abs /= f64); + +impl Rem for Abs { + type Output = Self; + + fn rem(self, other: Self) -> Self::Output { + Self(self.0 % other.0) + } +} + +impl Sum for Abs { + fn sum>(iter: I) -> Self { + Self(iter.map(|s| s.0).sum()) + } +} + +impl<'a> Sum<&'a Self> for Abs { + fn sum>(iter: I) -> Self { + Self(iter.map(|s| s.0).sum()) + } +} + +/// Different units of absolute measurement. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub enum AbsUnit { + /// Points. + Pt, + /// Millimeters. + Mm, + /// Centimeters. + Cm, + /// Inches. + In, +} + +impl AbsUnit { + /// How many raw units correspond to a value of `1.0` in this unit. + fn raw_scale(self) -> f64 { + match self { + AbsUnit::Pt => 1.0, + AbsUnit::Mm => 2.83465, + AbsUnit::Cm => 28.3465, + AbsUnit::In => 72.0, + } + } +} + +impl Debug for AbsUnit { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + AbsUnit::Mm => "mm", + AbsUnit::Pt => "pt", + AbsUnit::Cm => "cm", + AbsUnit::In => "in", + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_length_unit_conversion() { + assert!((Abs::mm(150.0).to_cm() - 15.0) < 1e-4); + } +} diff --git a/src/geom/align.rs b/src/geom/align.rs index 3d5f96e5f..a7ee27639 100644 --- a/src/geom/align.rs +++ b/src/geom/align.rs @@ -19,16 +19,16 @@ pub enum Align { impl Align { /// Top-left alignment. - pub const LEFT_TOP: Spec = Spec { x: Align::Left, y: Align::Top }; + pub const LEFT_TOP: Axes = Axes { x: Align::Left, y: Align::Top }; /// Center-horizon alignment. - pub const CENTER_HORIZON: Spec = Spec { x: Align::Center, y: Align::Horizon }; + pub const CENTER_HORIZON: Axes = Axes { x: Align::Center, y: Align::Horizon }; /// The axis this alignment belongs to. - pub const fn axis(self) -> SpecAxis { + pub const fn axis(self) -> Axis { match self { - Self::Left | Self::Center | Self::Right => SpecAxis::Horizontal, - Self::Top | Self::Horizon | Self::Bottom => SpecAxis::Vertical, + Self::Left | Self::Center | Self::Right => Axis::X, + Self::Top | Self::Horizon | Self::Bottom => Axis::Y, } } @@ -44,12 +44,13 @@ impl Align { } } - /// Returns the position of this alignment in the given length. - pub fn position(self, length: Length) -> Length { + /// Returns the position of this alignment in a container with the given + /// extentq. + pub fn position(self, extent: Abs) -> Abs { match self { - Self::Left | Self::Top => Length::zero(), - Self::Center | Self::Horizon => length / 2.0, - Self::Right | Self::Bottom => length, + Self::Left | Self::Top => Abs::zero(), + Self::Center | Self::Horizon => extent / 2.0, + Self::Right | Self::Bottom => extent, } } } diff --git a/src/geom/axes.rs b/src/geom/axes.rs new file mode 100644 index 000000000..bfc40c2ec --- /dev/null +++ b/src/geom/axes.rs @@ -0,0 +1,263 @@ +use std::any::Any; +use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not}; + +use super::*; + +/// A container with a horizontal and vertical component. +#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] +pub struct Axes { + /// The horizontal component. + pub x: T, + /// The vertical component. + pub y: T, +} + +impl Axes { + /// Create a new instance from the two components. + pub const fn new(x: T, y: T) -> Self { + Self { x, y } + } + + /// Create a new instance with two equal components. + pub fn splat(v: T) -> Self + where + T: Clone, + { + Self { x: v.clone(), y: v } + } + + /// Map the individual fields with `f`. + pub fn map(self, mut f: F) -> Axes + where + F: FnMut(T) -> U, + { + Axes { x: f(self.x), y: f(self.y) } + } + + /// Convert from `&Axes` to `Axes<&T>`. + pub fn as_ref(&self) -> Axes<&T> { + Axes { x: &self.x, y: &self.y } + } + + /// Convert from `&Axes` to `Axes<&::Target>`. + pub fn as_deref(&self) -> Axes<&T::Target> + where + T: Deref, + { + Axes { x: &self.x, y: &self.y } + } + + /// Convert from `&mut Axes` to `Axes<&mut T>`. + pub fn as_mut(&mut self) -> Axes<&mut T> { + Axes { x: &mut self.x, y: &mut self.y } + } + + /// Zip two instances into an instance over a tuple. + pub fn zip(self, other: Axes) -> Axes<(T, U)> { + Axes { + x: (self.x, other.x), + y: (self.y, other.y), + } + } + + /// Whether a condition is true for at least one of fields. + pub fn any(self, mut f: F) -> bool + where + F: FnMut(&T) -> bool, + { + f(&self.x) || f(&self.y) + } + + /// Whether a condition is true for both fields. + pub fn all(self, mut f: F) -> bool + where + F: FnMut(&T) -> bool, + { + f(&self.x) && f(&self.y) + } + + /// Filter the individual fields with a mask. + pub fn filter(self, mask: Axes) -> Axes> { + Axes { + x: if mask.x { Some(self.x) } else { None }, + y: if mask.y { Some(self.y) } else { None }, + } + } +} + +impl Axes { + /// Create a new instance with y set to its default value. + pub fn with_x(x: T) -> Self { + Self { x, y: T::default() } + } + + /// Create a new instance with x set to its default value. + pub fn with_y(y: T) -> Self { + Self { x: T::default(), y } + } +} + +impl Axes { + /// The component-wise minimum of this and another instance. + pub fn min(self, other: Self) -> Self { + Self { + x: self.x.min(other.x), + y: self.y.min(other.y), + } + } + + /// The component-wise minimum of this and another instance. + pub fn max(self, other: Self) -> Self { + Self { + x: self.x.max(other.x), + y: self.y.max(other.y), + } + } +} + +impl Get for Axes { + type Component = T; + + fn get(self, axis: Axis) -> T { + match axis { + Axis::X => self.x, + Axis::Y => self.y, + } + } + + fn get_mut(&mut self, axis: Axis) -> &mut T { + match axis { + Axis::X => &mut self.x, + Axis::Y => &mut self.y, + } + } +} + +impl Debug for Axes +where + T: Debug + 'static, +{ + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + if let Axes { x: Some(x), y: Some(y) } = + self.as_ref().map(|v| (v as &dyn Any).downcast_ref::()) + { + write!(f, "{:?}-{:?}", x, y) + } else if (&self.x as &dyn Any).is::() { + write!(f, "Size({:?}, {:?})", self.x, self.y) + } else { + write!(f, "Axes({:?}, {:?})", self.x, self.y) + } + } +} + +/// The two layouting axes. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub enum Axis { + /// The horizontal axis. + X, + /// The vertical axis. + Y, +} + +impl Axis { + /// The direction with the given positivity for this axis. + pub fn dir(self, positive: bool) -> Dir { + match (self, positive) { + (Self::X, true) => Dir::LTR, + (Self::X, false) => Dir::RTL, + (Self::Y, true) => Dir::TTB, + (Self::Y, false) => Dir::BTT, + } + } + + /// The other axis. + pub fn other(self) -> Self { + match self { + Self::X => Self::Y, + Self::Y => Self::X, + } + } +} + +impl Debug for Axis { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Self::X => "horizontal", + Self::Y => "vertical", + }) + } +} + +impl Axes> { + /// Unwrap the individual fields. + pub fn unwrap_or(self, other: Axes) -> Axes { + Axes { + x: self.x.unwrap_or(other.x), + y: self.y.unwrap_or(other.y), + } + } +} + +impl Axes { + /// Select `t.x` if `self.x` is true and `f.x` otherwise and same for `y`. + pub fn select(self, t: Axes, f: Axes) -> Axes { + Axes { + x: if self.x { t.x } else { f.x }, + y: if self.y { t.y } else { f.y }, + } + } +} + +impl Not for Axes { + type Output = Self; + + fn not(self) -> Self::Output { + Self { x: !self.x, y: !self.y } + } +} + +impl BitOr for Axes { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + Self { x: self.x | rhs.x, y: self.y | rhs.y } + } +} + +impl BitOr for Axes { + type Output = Self; + + fn bitor(self, rhs: bool) -> Self::Output { + Self { x: self.x | rhs, y: self.y | rhs } + } +} + +impl BitAnd for Axes { + type Output = Self; + + fn bitand(self, rhs: Self) -> Self::Output { + Self { x: self.x & rhs.x, y: self.y & rhs.y } + } +} + +impl BitAnd for Axes { + type Output = Self; + + fn bitand(self, rhs: bool) -> Self::Output { + Self { x: self.x & rhs, y: self.y & rhs } + } +} + +impl BitOrAssign for Axes { + fn bitor_assign(&mut self, rhs: Self) { + self.x |= rhs.x; + self.y |= rhs.y; + } +} + +impl BitAndAssign for Axes { + fn bitand_assign(&mut self, rhs: Self) { + self.x &= rhs.x; + self.y &= rhs.y; + } +} diff --git a/src/geom/dir.rs b/src/geom/dir.rs index b8244c0ab..b2fd6e5a2 100644 --- a/src/geom/dir.rs +++ b/src/geom/dir.rs @@ -15,10 +15,10 @@ pub enum Dir { impl Dir { /// The specific axis this direction belongs to. - pub const fn axis(self) -> SpecAxis { + pub const fn axis(self) -> Axis { match self { - Self::LTR | Self::RTL => SpecAxis::Horizontal, - Self::TTB | Self::BTT => SpecAxis::Vertical, + Self::LTR | Self::RTL => Axis::X, + Self::TTB | Self::BTT => Axis::Y, } } diff --git a/src/geom/ellipse.rs b/src/geom/ellipse.rs new file mode 100644 index 000000000..e734682e5 --- /dev/null +++ b/src/geom/ellipse.rs @@ -0,0 +1,26 @@ +use super::*; + +/// Produce a shape that approximates an axis-aligned ellipse. +pub fn ellipse(size: Size, fill: Option, stroke: Option) -> Shape { + // https://stackoverflow.com/a/2007782 + let z = Abs::zero(); + let rx = size.x / 2.0; + let ry = size.y / 2.0; + let m = 0.551784; + let mx = m * rx; + let my = m * ry; + let point = |x, y| Point::new(x + rx, y + ry); + + let mut path = Path::new(); + path.move_to(point(-rx, z)); + path.cubic_to(point(-rx, -my), point(-mx, -ry), point(z, -ry)); + path.cubic_to(point(mx, -ry), point(rx, -my), point(rx, z)); + path.cubic_to(point(rx, my), point(mx, ry), point(z, ry)); + path.cubic_to(point(-mx, ry), point(-rx, my), point(-rx, z)); + + Shape { + geometry: Geometry::Path(path), + stroke, + fill, + } +} diff --git a/src/geom/em.rs b/src/geom/em.rs index 99b1163ca..d0ad9d98b 100644 --- a/src/geom/em.rs +++ b/src/geom/em.rs @@ -7,7 +7,7 @@ use super::*; pub struct Em(Scalar); impl Em { - /// The zero length. + /// The zero em length. pub const fn zero() -> Self { Self(Scalar(0.0)) } @@ -28,7 +28,7 @@ impl Em { } /// Create an em length from a length at the given font size. - pub fn from_length(length: Length, font_size: Length) -> Self { + pub fn from_length(length: Abs, font_size: Abs) -> Self { let result = length / font_size; if result.is_finite() { Self(Scalar(result)) @@ -42,10 +42,10 @@ impl Em { (self.0).0 } - /// Convert to a length at the given font size. - pub fn at(self, font_size: Length) -> Length { + /// Convert to an absolute length at the given font size. + pub fn at(self, font_size: Abs) -> Abs { let resolved = font_size * self.get(); - if resolved.is_finite() { resolved } else { Length::zero() } + if resolved.is_finite() { resolved } else { Abs::zero() } } } diff --git a/src/geom/fraction.rs b/src/geom/fr.rs similarity index 73% rename from src/geom/fraction.rs rename to src/geom/fr.rs index 9d4b3aab1..a737c953f 100644 --- a/src/geom/fraction.rs +++ b/src/geom/fr.rs @@ -2,9 +2,9 @@ use super::*; /// A fraction of remaining space. #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Fraction(Scalar); +pub struct Fr(Scalar); -impl Fraction { +impl Fr { /// Takes up zero space: `0fr`. pub const fn zero() -> Self { Self(Scalar(0.0)) @@ -31,17 +31,17 @@ impl Fraction { } /// Determine this fraction's share in the remaining space. - pub fn share(self, total: Self, remaining: Length) -> Length { + pub fn share(self, total: Self, remaining: Abs) -> Abs { let ratio = self / total; if ratio.is_finite() && remaining.is_finite() { ratio * remaining } else { - Length::zero() + Abs::zero() } } } -impl Numeric for Fraction { +impl Numeric for Fr { fn zero() -> Self { Self::zero() } @@ -51,13 +51,13 @@ impl Numeric for Fraction { } } -impl Debug for Fraction { +impl Debug for Fr { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}fr", round_2(self.get())) } } -impl Neg for Fraction { +impl Neg for Fr { type Output = Self; fn neg(self) -> Self { @@ -65,7 +65,7 @@ impl Neg for Fraction { } } -impl Add for Fraction { +impl Add for Fr { type Output = Self; fn add(self, other: Self) -> Self { @@ -73,9 +73,9 @@ impl Add for Fraction { } } -sub_impl!(Fraction - Fraction -> Fraction); +sub_impl!(Fr - Fr -> Fr); -impl Mul for Fraction { +impl Mul for Fr { type Output = Self; fn mul(self, other: f64) -> Self { @@ -83,15 +83,15 @@ impl Mul for Fraction { } } -impl Mul for f64 { - type Output = Fraction; +impl Mul for f64 { + type Output = Fr; - fn mul(self, other: Fraction) -> Fraction { + fn mul(self, other: Fr) -> Fr { other * self } } -impl Div for Fraction { +impl Div for Fr { type Output = Self; fn div(self, other: f64) -> Self { @@ -99,7 +99,7 @@ impl Div for Fraction { } } -impl Div for Fraction { +impl Div for Fr { type Output = f64; fn div(self, other: Self) -> f64 { @@ -107,12 +107,12 @@ impl Div for Fraction { } } -assign_impl!(Fraction += Fraction); -assign_impl!(Fraction -= Fraction); -assign_impl!(Fraction *= f64); -assign_impl!(Fraction /= f64); +assign_impl!(Fr += Fr); +assign_impl!(Fr -= Fr); +assign_impl!(Fr *= f64); +assign_impl!(Fr /= f64); -impl Sum for Fraction { +impl Sum for Fr { fn sum>(iter: I) -> Self { Self(iter.map(|s| s.0).sum()) } diff --git a/src/geom/gen.rs b/src/geom/gen.rs deleted file mode 100644 index 462de357b..000000000 --- a/src/geom/gen.rs +++ /dev/null @@ -1,99 +0,0 @@ -use super::*; - -/// A container with a main and cross component. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Gen { - /// The main component. - pub cross: T, - /// The cross component. - pub main: T, -} - -impl Gen { - /// Create a new instance from the two components. - pub const fn new(cross: T, main: T) -> Self { - Self { cross, main } - } - - /// Create a new instance with two equal components. - pub fn splat(value: T) -> Self - where - T: Clone, - { - Self { cross: value.clone(), main: value } - } - - /// Maps the individual fields with `f`. - pub fn map(self, mut f: F) -> Gen - where - F: FnMut(T) -> U, - { - Gen { cross: f(self.cross), main: f(self.main) } - } - - /// Convert to the specific representation, given the current main axis. - pub fn to_spec(self, main: SpecAxis) -> Spec { - match main { - SpecAxis::Horizontal => Spec::new(self.main, self.cross), - SpecAxis::Vertical => Spec::new(self.cross, self.main), - } - } -} - -impl Gen { - /// The zero value. - pub fn zero() -> Self { - Self { - cross: Length::zero(), - main: Length::zero(), - } - } - - /// Convert to a point. - pub fn to_point(self, main: SpecAxis) -> Point { - self.to_spec(main).to_point() - } -} - -impl Get for Gen { - type Component = T; - - fn get(self, axis: GenAxis) -> T { - match axis { - GenAxis::Cross => self.cross, - GenAxis::Main => self.main, - } - } - - fn get_mut(&mut self, axis: GenAxis) -> &mut T { - match axis { - GenAxis::Cross => &mut self.cross, - GenAxis::Main => &mut self.main, - } - } -} - -impl Debug for Gen { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Gen({:?}, {:?})", self.cross, self.main) - } -} - -/// Two generic axes of a container. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum GenAxis { - /// The minor / inline axis. - Cross, - /// The major / block axis. - Main, -} - -impl GenAxis { - /// The other axis. - pub fn other(self) -> Self { - match self { - Self::Cross => Self::Main, - Self::Main => Self::Cross, - } - } -} diff --git a/src/geom/length.rs b/src/geom/length.rs index 773fd2fd5..cd526f992 100644 --- a/src/geom/length.rs +++ b/src/geom/length.rs @@ -1,124 +1,41 @@ use super::*; -/// An absolute length. -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Length(Scalar); +/// A length, possibly expressed with contextual units. +/// +/// Currently supports absolute and font-relative units, but support could quite +/// easily be extended to other units. +#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] +pub struct Length { + /// The absolute part. + pub abs: Abs, + /// The font-relative part. + pub em: Em, +} impl Length { /// The zero length. pub const fn zero() -> Self { - Self(Scalar(0.0)) + Self { abs: Abs::zero(), em: Em::zero() } } - /// The infinite length. - pub const fn inf() -> Self { - Self(Scalar(f64::INFINITY)) - } - - /// Create a length from a number of raw units. - pub const fn raw(raw: f64) -> Self { - Self(Scalar(raw)) - } - - /// Create a length from a value in a unit. - pub fn with_unit(val: f64, unit: LengthUnit) -> Self { - Self(Scalar(val * unit.raw_scale())) - } - - /// Create a length from a number of points. - pub fn pt(pt: f64) -> Self { - Self::with_unit(pt, LengthUnit::Pt) - } - - /// Create a length from a number of millimeters. - pub fn mm(mm: f64) -> Self { - Self::with_unit(mm, LengthUnit::Mm) - } - - /// Create a length from a number of centimeters. - pub fn cm(cm: f64) -> Self { - Self::with_unit(cm, LengthUnit::Cm) - } - - /// Create a length from a number of inches. - pub fn inches(inches: f64) -> Self { - Self::with_unit(inches, LengthUnit::In) - } - - /// Get the value of this length in raw units. - pub const fn to_raw(self) -> f64 { - (self.0).0 - } - - /// Get the value of this length in a unit. - pub fn to_unit(self, unit: LengthUnit) -> f64 { - self.to_raw() / unit.raw_scale() - } - - /// Convert this to a number of points. - pub fn to_pt(self) -> f64 { - self.to_unit(LengthUnit::Pt) - } - - /// Convert this to a number of millimeters. - pub fn to_mm(self) -> f64 { - self.to_unit(LengthUnit::Mm) - } - - /// Convert this to a number of centimeters. - pub fn to_cm(self) -> f64 { - self.to_unit(LengthUnit::Cm) - } - - /// Convert this to a number of inches. - pub fn to_inches(self) -> f64 { - self.to_unit(LengthUnit::In) - } - - /// The absolute value of this length. - pub fn abs(self) -> Self { - Self::raw(self.to_raw().abs()) - } - - /// The minimum of this and another length. - pub fn min(self, other: Self) -> Self { - Self(self.0.min(other.0)) - } - - /// Set to the minimum of this and another length. - pub fn set_min(&mut self, other: Self) { - *self = (*self).min(other); - } - - /// The maximum of this and another length. - pub fn max(self, other: Self) -> Self { - Self(self.0.max(other.0)) - } - - /// Set to the maximum of this and another length. - pub fn set_max(&mut self, other: Self) { - *self = (*self).max(other); - } - - /// Whether the other length fits into this one (i.e. is smaller). Allows - /// for a bit of slack. - pub fn fits(self, other: Self) -> bool { - self.0 + 1e-6 >= other.0 - } - - /// Compares two lengths for whether they are approximately equal. - pub fn approx_eq(self, other: Self) -> bool { - self == other || (self - other).to_raw().abs() < 1e-6 - } - - /// Perform a checked division by a number, returning zero if the result - /// is not finite. - pub fn safe_div(self, number: f64) -> Self { - let result = self.to_raw() / number; - if result.is_finite() { - Self::raw(result) + /// Try to divide two lengths. + pub fn try_div(self, other: Self) -> Option { + if self.abs.is_zero() && other.abs.is_zero() { + Some(self.em / other.em) + } else if self.em.is_zero() && other.em.is_zero() { + Some(self.abs / other.abs) } else { - Self::zero() + None + } + } +} + +impl Debug for Length { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match (self.abs.is_zero(), self.em.is_zero()) { + (false, false) => write!(f, "{:?} + {:?}", self.abs, self.em), + (true, false) => self.em.fmt(f), + (_, true) => self.abs.fmt(f), } } } @@ -129,29 +46,56 @@ impl Numeric for Length { } fn is_finite(self) -> bool { - self.0.is_finite() + self.abs.is_finite() && self.em.is_finite() } } -impl Debug for Length { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}pt", round_2(self.to_pt())) +impl PartialOrd for Length { + fn partial_cmp(&self, other: &Self) -> Option { + if self.em.is_zero() && other.em.is_zero() { + self.abs.partial_cmp(&other.abs) + } else if self.abs.is_zero() && other.abs.is_zero() { + self.em.partial_cmp(&other.em) + } else { + None + } + } +} + +impl From for Length { + fn from(abs: Abs) -> Self { + Self { abs, em: Em::zero() } + } +} + +impl From for Length { + fn from(em: Em) -> Self { + Self { abs: Abs::zero(), em } + } +} + +impl From for Rel { + fn from(abs: Abs) -> Self { + Rel::from(Length::from(abs)) } } impl Neg for Length { type Output = Self; - fn neg(self) -> Self { - Self(-self.0) + fn neg(self) -> Self::Output { + Self { abs: -self.abs, em: -self.em } } } impl Add for Length { type Output = Self; - fn add(self, other: Self) -> Self { - Self(self.0 + other.0) + fn add(self, rhs: Self) -> Self::Output { + Self { + abs: self.abs + rhs.abs, + em: self.em + rhs.em, + } } } @@ -160,32 +104,16 @@ sub_impl!(Length - Length -> Length); impl Mul for Length { type Output = Self; - fn mul(self, other: f64) -> Self { - Self(self.0 * other) - } -} - -impl Mul for f64 { - type Output = Length; - - fn mul(self, other: Length) -> Length { - other * self + fn mul(self, rhs: f64) -> Self::Output { + Self { abs: self.abs * rhs, em: self.em * rhs } } } impl Div for Length { type Output = Self; - fn div(self, other: f64) -> Self { - Self(self.0 / other) - } -} - -impl Div for Length { - type Output = f64; - - fn div(self, other: Self) -> f64 { - self.to_raw() / other.to_raw() + fn div(self, rhs: f64) -> Self::Output { + Self { abs: self.abs / rhs, em: self.em / rhs } } } @@ -193,69 +121,3 @@ assign_impl!(Length += Length); assign_impl!(Length -= Length); assign_impl!(Length *= f64); assign_impl!(Length /= f64); - -impl Rem for Length { - type Output = Self; - - fn rem(self, other: Self) -> Self::Output { - Self(self.0 % other.0) - } -} - -impl Sum for Length { - fn sum>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} - -impl<'a> Sum<&'a Self> for Length { - fn sum>(iter: I) -> Self { - Self(iter.map(|s| s.0).sum()) - } -} - -/// Different units of length measurement. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub enum LengthUnit { - /// Points. - Pt, - /// Millimeters. - Mm, - /// Centimeters. - Cm, - /// Inches. - In, -} - -impl LengthUnit { - /// How many raw units correspond to a value of `1.0` in this unit. - fn raw_scale(self) -> f64 { - match self { - LengthUnit::Pt => 1.0, - LengthUnit::Mm => 2.83465, - LengthUnit::Cm => 28.3465, - LengthUnit::In => 72.0, - } - } -} - -impl Debug for LengthUnit { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - LengthUnit::Mm => "mm", - LengthUnit::Pt => "pt", - LengthUnit::Cm => "cm", - LengthUnit::In => "in", - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_length_unit_conversion() { - assert!((Length::mm(150.0).to_cm() - 15.0) < 1e-4); - } -} diff --git a/src/geom/mod.rs b/src/geom/mod.rs index fc8ccb6c0..6e2b8f9ba 100644 --- a/src/geom/mod.rs +++ b/src/geom/mod.rs @@ -2,42 +2,46 @@ #[macro_use] mod macros; +mod abs; mod align; mod angle; +mod axes; mod corners; mod dir; +mod ellipse; mod em; -mod fraction; -mod gen; +mod fr; mod length; mod paint; mod path; mod point; mod ratio; -mod rect; -mod relative; +mod rel; +mod rounded; mod scalar; mod sides; -mod spec; +mod size; mod transform; +pub use abs::*; pub use align::*; pub use angle::*; +pub use axes::*; pub use corners::*; pub use dir::*; +pub use ellipse::*; pub use em::*; -pub use fraction::*; -pub use gen::*; +pub use fr::*; pub use length::*; pub use paint::*; pub use path::*; pub use point::*; pub use ratio::*; -pub use rect::*; -pub use relative::*; +pub use rel::*; +pub use rounded::*; pub use scalar::*; pub use sides::*; -pub use spec::*; +pub use size::*; pub use transform::*; use std::cmp::Ordering; @@ -82,8 +86,6 @@ pub enum Geometry { Line(Point), /// A rectangle with its origin in the topleft corner. Rect(Size), - /// A ellipse with its origin in the topleft corner. - Ellipse(Size), /// A bezier path. Path(Path), } diff --git a/src/geom/paint.rs b/src/geom/paint.rs index 02ae3ee56..c5bbefa4c 100644 --- a/src/geom/paint.rs +++ b/src/geom/paint.rs @@ -394,14 +394,14 @@ pub struct Stroke { /// The stroke's paint. pub paint: Paint, /// The stroke's thickness. - pub thickness: Length, + pub thickness: Abs, } impl Default for Stroke { fn default() -> Self { Self { paint: Paint::Solid(Color::BLACK.into()), - thickness: Length::pt(1.0), + thickness: Abs::pt(1.0), } } } diff --git a/src/geom/path.rs b/src/geom/path.rs index d0c3c75d0..ffd3db1c6 100644 --- a/src/geom/path.rs +++ b/src/geom/path.rs @@ -21,7 +21,7 @@ impl Path { /// Create a path that describes a rectangle. pub fn rect(size: Size) -> Self { - let z = Length::zero(); + let z = Abs::zero(); let point = Point::new; let mut path = Self::new(); path.move_to(point(z, z)); @@ -32,25 +32,6 @@ impl Path { path } - /// Create a path that approximates an axis-aligned ellipse. - pub fn ellipse(size: Size) -> Self { - // https://stackoverflow.com/a/2007782 - let z = Length::zero(); - let rx = size.x / 2.0; - let ry = size.y / 2.0; - let m = 0.551784; - let mx = m * rx; - let my = m * ry; - let point = |x, y| Point::new(x + rx, y + ry); - let mut path = Self::new(); - path.move_to(point(-rx, z)); - path.cubic_to(point(-rx, -my), point(-mx, -ry), point(z, -ry)); - path.cubic_to(point(mx, -ry), point(rx, -my), point(rx, z)); - path.cubic_to(point(rx, my), point(mx, ry), point(z, ry)); - path.cubic_to(point(-mx, ry), point(-rx, my), point(-rx, z)); - path - } - /// Push a [`MoveTo`](PathElement::MoveTo) element. pub fn move_to(&mut self, p: Point) { self.0.push(PathElement::MoveTo(p)); @@ -71,22 +52,3 @@ impl Path { self.0.push(PathElement::ClosePath); } } - -/// Get the control points for a bezier curve that describes a circular arc for -/// a start point, an end point and a center of the circle whose arc connects -/// the two. -pub fn bezier_arc(start: Point, center: Point, end: Point) -> [Point; 4] { - // https://stackoverflow.com/a/44829356/1567835 - let a = start - center; - let b = end - center; - - let q1 = a.x.to_raw() * a.x.to_raw() + a.y.to_raw() * a.y.to_raw(); - let q2 = q1 + a.x.to_raw() * b.x.to_raw() + a.y.to_raw() * b.y.to_raw(); - let k2 = (4.0 / 3.0) * ((2.0 * q1 * q2).sqrt() - q2) - / (a.x.to_raw() * b.y.to_raw() - a.y.to_raw() * b.x.to_raw()); - - let control_1 = Point::new(center.x + a.x - k2 * a.y, center.y + a.y + k2 * a.x); - let control_2 = Point::new(center.x + b.x + k2 * b.y, center.y + b.y - k2 * b.x); - - [start, control_1, control_2, end] -} diff --git a/src/geom/point.rs b/src/geom/point.rs index 7f67d353b..34d3dcd8e 100644 --- a/src/geom/point.rs +++ b/src/geom/point.rs @@ -4,35 +4,35 @@ use super::*; #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] pub struct Point { /// The x coordinate. - pub x: Length, + pub x: Abs, /// The y coordinate. - pub y: Length, + pub y: Abs, } impl Point { /// The origin point. pub const fn zero() -> Self { - Self { x: Length::zero(), y: Length::zero() } + Self { x: Abs::zero(), y: Abs::zero() } } /// Create a new point from x and y coordinates. - pub const fn new(x: Length, y: Length) -> Self { + pub const fn new(x: Abs, y: Abs) -> Self { Self { x, y } } /// Create an instance with two equal components. - pub const fn splat(value: Length) -> Self { + pub const fn splat(value: Abs) -> Self { Self { x: value, y: value } } /// Create a new point with y set to zero. - pub const fn with_x(x: Length) -> Self { - Self { x, y: Length::zero() } + pub const fn with_x(x: Abs) -> Self { + Self { x, y: Abs::zero() } } /// Create a new point with x set to zero. - pub const fn with_y(y: Length) -> Self { - Self { x: Length::zero(), y } + pub const fn with_y(y: Abs) -> Self { + Self { x: Abs::zero(), y } } /// Transform the point with the given transformation. @@ -54,20 +54,20 @@ impl Numeric for Point { } } -impl Get for Point { - type Component = Length; +impl Get for Point { + type Component = Abs; - fn get(self, axis: SpecAxis) -> Length { + fn get(self, axis: Axis) -> Abs { match axis { - SpecAxis::Horizontal => self.x, - SpecAxis::Vertical => self.y, + Axis::X => self.x, + Axis::Y => self.y, } } - fn get_mut(&mut self, axis: SpecAxis) -> &mut Length { + fn get_mut(&mut self, axis: Axis) -> &mut Abs { match axis { - SpecAxis::Horizontal => &mut self.x, - SpecAxis::Vertical => &mut self.y, + Axis::X => &mut self.x, + Axis::Y => &mut self.y, } } } diff --git a/src/geom/rect.rs b/src/geom/rect.rs deleted file mode 100644 index dfea2c452..000000000 --- a/src/geom/rect.rs +++ /dev/null @@ -1,184 +0,0 @@ -use super::*; - -use std::mem; - -/// A rectangle with rounded corners. -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct RoundedRect { - /// The size of the rectangle. - pub size: Size, - /// The radius at each corner. - pub radius: Corners, -} - -impl RoundedRect { - /// Create a new rounded rectangle. - pub fn new(size: Size, radius: Corners) -> Self { - Self { size, radius } - } - - /// Output all constituent shapes of the rectangle in order. The last one is - /// in the foreground. The function will output multiple items if the stroke - /// properties differ by side. - pub fn shapes( - self, - fill: Option, - stroke: Sides>, - ) -> Vec { - let mut res = vec![]; - if fill.is_some() || (stroke.iter().any(Option::is_some) && stroke.is_uniform()) { - res.push(Shape { - geometry: self.fill_geometry(), - fill, - stroke: if stroke.is_uniform() { stroke.top } else { None }, - }); - } - - if !stroke.is_uniform() { - for (path, stroke) in self.stroke_segments(stroke) { - if stroke.is_some() { - res.push(Shape { - geometry: Geometry::Path(path), - fill: None, - stroke, - }); - } - } - } - - res - } - - /// Output the shape of the rectangle as a path or primitive rectangle, - /// depending on whether it is rounded. - fn fill_geometry(self) -> Geometry { - if self.radius.iter().copied().all(Length::is_zero) { - Geometry::Rect(self.size) - } else { - let mut paths = self.stroke_segments(Sides::splat(None)); - assert_eq!(paths.len(), 1); - Geometry::Path(paths.pop().unwrap().0) - } - } - - /// Output the minimum number of paths along the rectangles border. - fn stroke_segments( - self, - strokes: Sides>, - ) -> Vec<(Path, Option)> { - let mut res = vec![]; - - let mut connection = Connection::default(); - let mut path = Path::new(); - let mut always_continuous = true; - - for side in [Side::Top, Side::Right, Side::Bottom, Side::Left] { - let continuous = strokes.get(side) == strokes.get(side.next_cw()); - connection = connection.advance(continuous && side != Side::Left); - always_continuous &= continuous; - - draw_side( - &mut path, - side, - self.size, - self.radius.get(side.start_corner()), - self.radius.get(side.end_corner()), - connection, - ); - - if !continuous { - res.push((mem::take(&mut path), strokes.get(side))); - } - } - - if always_continuous { - path.close_path(); - } - - if !path.0.is_empty() { - res.push((path, strokes.left)); - } - - res - } -} - -/// Draws one side of the rounded rectangle. Will always draw the left arc. The -/// right arc will be drawn halfway if and only if there is no connection. -fn draw_side( - path: &mut Path, - side: Side, - size: Size, - start_radius: Length, - end_radius: Length, - connection: Connection, -) { - let angle_left = Angle::deg(if connection.prev { 90.0 } else { 45.0 }); - let angle_right = Angle::deg(if connection.next { 90.0 } else { 45.0 }); - let length = size.get(side.axis()); - - // The arcs for a border of the rectangle along the x-axis, starting at (0,0). - let p1 = Point::with_x(start_radius); - let mut arc1 = bezier_arc( - p1 + Point::new( - -angle_left.sin() * start_radius, - (1.0 - angle_left.cos()) * start_radius, - ), - Point::new(start_radius, start_radius), - p1, - ); - - let p2 = Point::with_x(length - end_radius); - let mut arc2 = bezier_arc( - p2, - Point::new(length - end_radius, end_radius), - p2 + Point::new( - angle_right.sin() * end_radius, - (1.0 - angle_right.cos()) * end_radius, - ), - ); - - let transform = match side { - Side::Left => Transform::rotate(Angle::deg(-90.0)) - .post_concat(Transform::translate(Length::zero(), size.y)), - Side::Bottom => Transform::rotate(Angle::deg(180.0)) - .post_concat(Transform::translate(size.x, size.y)), - Side::Right => Transform::rotate(Angle::deg(90.0)) - .post_concat(Transform::translate(size.x, Length::zero())), - _ => Transform::identity(), - }; - - arc1 = arc1.map(|x| x.transform(transform)); - arc2 = arc2.map(|x| x.transform(transform)); - - if !connection.prev { - path.move_to(if start_radius.is_zero() { arc1[3] } else { arc1[0] }); - } - - if !start_radius.is_zero() { - path.cubic_to(arc1[1], arc1[2], arc1[3]); - } - - path.line_to(arc2[0]); - - if !connection.next && !end_radius.is_zero() { - path.cubic_to(arc2[1], arc2[2], arc2[3]); - } -} - -/// Indicates which sides of the border strokes in a 2D polygon are connected to -/// their neighboring sides. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] -struct Connection { - prev: bool, - next: bool, -} - -impl Connection { - /// Advance to the next clockwise side of the polygon. The argument - /// indicates whether the border is connected on the right side of the next - /// edge. - pub fn advance(self, next: bool) -> Self { - Self { prev: self.next, next } - } -} diff --git a/src/geom/relative.rs b/src/geom/rel.rs similarity index 70% rename from src/geom/relative.rs rename to src/geom/rel.rs index 42e60d39f..5c3b0b435 100644 --- a/src/geom/relative.rs +++ b/src/geom/rel.rs @@ -2,14 +2,14 @@ use super::*; /// A value that is composed of a relative and an absolute part. #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Relative { +pub struct Rel { /// The relative part. pub rel: Ratio, /// The absolute part. pub abs: T, } -impl Relative { +impl Rel { /// The zero relative. pub fn zero() -> Self { Self { rel: Ratio::zero(), abs: T::zero() } @@ -41,16 +41,29 @@ impl Relative { } /// Map the absolute part with `f`. - pub fn map(self, f: F) -> Relative + pub fn map(self, f: F) -> Rel where F: FnOnce(T) -> U, U: Numeric, { - Relative { rel: self.rel, abs: f(self.abs) } + Rel { rel: self.rel, abs: f(self.abs) } } } -impl Debug for Relative { +impl Rel { + /// Try to divide two relative lengths. + pub fn try_div(self, other: Self) -> Option { + if self.rel.is_zero() && other.rel.is_zero() { + self.abs.try_div(other.abs) + } else if self.abs.is_zero() && other.abs.is_zero() { + Some(self.rel / other.rel) + } else { + None + } + } +} + +impl Debug for Rel { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match (self.rel.is_zero(), self.abs.is_zero()) { (false, false) => write!(f, "{:?} + {:?}", self.rel, self.abs), @@ -60,19 +73,19 @@ impl Debug for Relative { } } -impl From for Relative { +impl From for Rel { fn from(abs: T) -> Self { Self { rel: Ratio::zero(), abs } } } -impl From for Relative { +impl From for Rel { fn from(rel: Ratio) -> Self { Self { rel, abs: T::zero() } } } -impl PartialOrd for Relative { +impl PartialOrd for Rel { fn partial_cmp(&self, other: &Self) -> Option { if self.rel.is_zero() && other.rel.is_zero() { self.abs.partial_cmp(&other.abs) @@ -84,7 +97,7 @@ impl PartialOrd for Relative { } } -impl Neg for Relative { +impl Neg for Rel { type Output = Self; fn neg(self) -> Self { @@ -92,7 +105,7 @@ impl Neg for Relative { } } -impl Add for Relative { +impl Add for Rel { type Output = Self; fn add(self, other: Self) -> Self::Output { @@ -103,7 +116,7 @@ impl Add for Relative { } } -impl Sub for Relative { +impl Sub for Rel { type Output = Self; fn sub(self, other: Self) -> Self::Output { @@ -111,7 +124,7 @@ impl Sub for Relative { } } -impl Mul for Relative { +impl Mul for Rel { type Output = Self; fn mul(self, other: f64) -> Self::Output { @@ -122,15 +135,15 @@ impl Mul for Relative { } } -impl Mul> for f64 { - type Output = Relative; +impl Mul> for f64 { + type Output = Rel; - fn mul(self, other: Relative) -> Self::Output { + fn mul(self, other: Rel) -> Self::Output { other * self } } -impl Div for Relative { +impl Div for Rel { type Output = Self; fn div(self, other: f64) -> Self::Output { @@ -141,28 +154,28 @@ impl Div for Relative { } } -impl AddAssign for Relative { +impl AddAssign for Rel { fn add_assign(&mut self, other: Self) { self.rel += other.rel; self.abs += other.abs; } } -impl SubAssign for Relative { +impl SubAssign for Rel { fn sub_assign(&mut self, other: Self) { self.rel -= other.rel; self.abs -= other.abs; } } -impl> MulAssign for Relative { +impl> MulAssign for Rel { fn mul_assign(&mut self, other: f64) { self.rel *= other; self.abs *= other; } } -impl> DivAssign for Relative { +impl> DivAssign for Rel { fn div_assign(&mut self, other: f64) { self.rel /= other; self.abs /= other; @@ -170,25 +183,25 @@ impl> DivAssign for Relative { } impl Add for Ratio { - type Output = Relative; + type Output = Rel; fn add(self, other: T) -> Self::Output { - Relative::from(self) + Relative::from(other) + Rel::from(self) + Rel::from(other) } } -impl Add for Relative { +impl Add for Rel { type Output = Self; fn add(self, other: T) -> Self::Output { - self + Relative::from(other) + self + Rel::from(other) } } -impl Add for Relative { +impl Add for Rel { type Output = Self; fn add(self, other: Ratio) -> Self::Output { - self + Relative::from(other) + self + Rel::from(other) } } diff --git a/src/geom/rounded.rs b/src/geom/rounded.rs new file mode 100644 index 000000000..70d351ee8 --- /dev/null +++ b/src/geom/rounded.rs @@ -0,0 +1,187 @@ +use super::*; + +use std::mem; + +/// Produce shapes that together make up a rounded rectangle. +pub fn rounded_rect( + size: Size, + radius: Corners, + fill: Option, + stroke: Sides>, +) -> Vec { + let mut res = vec![]; + if fill.is_some() || (stroke.iter().any(Option::is_some) && stroke.is_uniform()) { + res.push(Shape { + geometry: fill_geometry(size, radius), + fill, + stroke: if stroke.is_uniform() { stroke.top } else { None }, + }); + } + + if !stroke.is_uniform() { + for (path, stroke) in stroke_segments(size, radius, stroke) { + if stroke.is_some() { + res.push(Shape { + geometry: Geometry::Path(path), + fill: None, + stroke, + }); + } + } + } + + res +} + +/// Output the shape of the rectangle as a path or primitive rectangle, +/// depending on whether it is rounded. +fn fill_geometry(size: Size, radius: Corners) -> Geometry { + if radius.iter().copied().all(Abs::is_zero) { + Geometry::Rect(size) + } else { + let mut paths = stroke_segments(size, radius, Sides::splat(None)); + assert_eq!(paths.len(), 1); + Geometry::Path(paths.pop().unwrap().0) + } +} + +/// Output the minimum number of paths along the rectangles border. +fn stroke_segments( + size: Size, + radius: Corners, + stroke: Sides>, +) -> Vec<(Path, Option)> { + let mut res = vec![]; + + let mut connection = Connection::default(); + let mut path = Path::new(); + let mut always_continuous = true; + + for side in [Side::Top, Side::Right, Side::Bottom, Side::Left] { + let continuous = stroke.get(side) == stroke.get(side.next_cw()); + connection = connection.advance(continuous && side != Side::Left); + always_continuous &= continuous; + + draw_side( + &mut path, + side, + size, + radius.get(side.start_corner()), + radius.get(side.end_corner()), + connection, + ); + + if !continuous { + res.push((mem::take(&mut path), stroke.get(side))); + } + } + + if always_continuous { + path.close_path(); + } + + if !path.0.is_empty() { + res.push((path, stroke.left)); + } + + res +} + +/// Draws one side of the rounded rectangle. Will always draw the left arc. The +/// right arc will be drawn halfway if and only if there is no connection. +fn draw_side( + path: &mut Path, + side: Side, + size: Size, + start_radius: Abs, + end_radius: Abs, + connection: Connection, +) { + let angle_left = Angle::deg(if connection.prev { 90.0 } else { 45.0 }); + let angle_right = Angle::deg(if connection.next { 90.0 } else { 45.0 }); + let length = size.get(side.axis()); + + // The arcs for a border of the rectangle along the x-axis, starting at (0,0). + let p1 = Point::with_x(start_radius); + let mut arc1 = bezier_arc( + p1 + Point::new( + -angle_left.sin() * start_radius, + (1.0 - angle_left.cos()) * start_radius, + ), + Point::new(start_radius, start_radius), + p1, + ); + + let p2 = Point::with_x(length - end_radius); + let mut arc2 = bezier_arc( + p2, + Point::new(length - end_radius, end_radius), + p2 + Point::new( + angle_right.sin() * end_radius, + (1.0 - angle_right.cos()) * end_radius, + ), + ); + + let transform = match side { + Side::Left => Transform::rotate(Angle::deg(-90.0)) + .post_concat(Transform::translate(Abs::zero(), size.y)), + Side::Bottom => Transform::rotate(Angle::deg(180.0)) + .post_concat(Transform::translate(size.x, size.y)), + Side::Right => Transform::rotate(Angle::deg(90.0)) + .post_concat(Transform::translate(size.x, Abs::zero())), + _ => Transform::identity(), + }; + + arc1 = arc1.map(|x| x.transform(transform)); + arc2 = arc2.map(|x| x.transform(transform)); + + if !connection.prev { + path.move_to(if start_radius.is_zero() { arc1[3] } else { arc1[0] }); + } + + if !start_radius.is_zero() { + path.cubic_to(arc1[1], arc1[2], arc1[3]); + } + + path.line_to(arc2[0]); + + if !connection.next && !end_radius.is_zero() { + path.cubic_to(arc2[1], arc2[2], arc2[3]); + } +} + +/// Get the control points for a bezier curve that describes a circular arc for +/// a start point, an end point and a center of the circle whose arc connects +/// the two. +fn bezier_arc(start: Point, center: Point, end: Point) -> [Point; 4] { + // https://stackoverflow.com/a/44829356/1567835 + let a = start - center; + let b = end - center; + + let q1 = a.x.to_raw() * a.x.to_raw() + a.y.to_raw() * a.y.to_raw(); + let q2 = q1 + a.x.to_raw() * b.x.to_raw() + a.y.to_raw() * b.y.to_raw(); + let k2 = (4.0 / 3.0) * ((2.0 * q1 * q2).sqrt() - q2) + / (a.x.to_raw() * b.y.to_raw() - a.y.to_raw() * b.x.to_raw()); + + let control_1 = Point::new(center.x + a.x - k2 * a.y, center.y + a.y + k2 * a.x); + let control_2 = Point::new(center.x + b.x + k2 * b.y, center.y + b.y - k2 * b.x); + + [start, control_1, control_2, end] +} + +/// Indicates which sides of the border strokes in a 2D polygon are connected to +/// their neighboring sides. +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] +struct Connection { + prev: bool, + next: bool, +} + +impl Connection { + /// Advance to the next clockwise side of the polygon. The argument + /// indicates whether the border is connected on the right side of the next + /// edge. + pub fn advance(self, next: bool) -> Self { + Self { prev: self.next, next } + } +} diff --git a/src/geom/sides.rs b/src/geom/sides.rs index 727489162..9b8d9a6b7 100644 --- a/src/geom/sides.rs +++ b/src/geom/sides.rs @@ -74,14 +74,14 @@ impl Sides { impl Sides { /// Sums up `left` and `right` into `x`, and `top` and `bottom` into `y`. - pub fn sum_by_axis(self) -> Spec { - Spec::new(self.left + self.right, self.top + self.bottom) + pub fn sum_by_axis(self) -> Axes { + Axes::new(self.left + self.right, self.top + self.bottom) } } -impl Sides> { +impl Sides> { /// Evaluate the sides relative to the given `size`. - pub fn relative_to(self, size: Size) -> Sides { + pub fn relative_to(self, size: Size) -> Sides { Sides { left: self.left.relative_to(size.x), top: self.top.relative_to(size.y), @@ -173,10 +173,10 @@ impl Side { } /// Return the corresponding axis. - pub fn axis(self) -> SpecAxis { + pub fn axis(self) -> Axis { match self { - Self::Left | Self::Right => SpecAxis::Vertical, - Self::Top | Self::Bottom => SpecAxis::Horizontal, + Self::Left | Self::Right => Axis::Y, + Self::Top | Self::Bottom => Axis::X, } } } diff --git a/src/geom/size.rs b/src/geom/size.rs new file mode 100644 index 000000000..a2e32b779 --- /dev/null +++ b/src/geom/size.rs @@ -0,0 +1,78 @@ +use super::*; + +/// A size in 2D. +pub type Size = Axes; + +impl Size { + /// The zero value. + pub const fn zero() -> Self { + Self { x: Abs::zero(), y: Abs::zero() } + } + + /// Whether the other size fits into this one (smaller width and height). + pub fn fits(self, other: Self) -> bool { + self.x.fits(other.x) && self.y.fits(other.y) + } + + /// Convert to a point. + pub fn to_point(self) -> Point { + Point::new(self.x, self.y) + } +} + +impl Numeric for Size { + fn zero() -> Self { + Self::zero() + } + + fn is_finite(self) -> bool { + self.x.is_finite() && self.y.is_finite() + } +} + +impl Neg for Size { + type Output = Self; + + fn neg(self) -> Self { + Self { x: -self.x, y: -self.y } + } +} + +impl Add for Size { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { x: self.x + other.x, y: self.y + other.y } + } +} + +sub_impl!(Size - Size -> Size); + +impl Mul for Size { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self { x: self.x * other, y: self.y * 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 { x: self.x / other, y: self.y / 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 deleted file mode 100644 index e80df8702..000000000 --- a/src/geom/spec.rs +++ /dev/null @@ -1,358 +0,0 @@ -use std::any::Any; -use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not}; - -use super::*; - -/// A container with a horizontal and vertical component. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Spec { - /// The horizontal component. - pub x: T, - /// The vertical component. - pub y: T, -} - -impl Spec { - /// Create a new instance from the two components. - pub const fn new(x: T, y: T) -> Self { - Self { x, y } - } - - /// Create a new instance with two equal components. - pub fn splat(v: T) -> Self - where - T: Clone, - { - Self { x: v.clone(), y: v } - } - - /// Map the individual fields with `f`. - pub fn map(self, mut f: F) -> Spec - where - F: FnMut(T) -> U, - { - Spec { x: f(self.x), y: f(self.y) } - } - - /// Convert from `&Spec` to `Spec<&T>`. - pub fn as_ref(&self) -> Spec<&T> { - Spec { x: &self.x, y: &self.y } - } - - /// Convert from `&Spec` to `Spec<&::Target>`. - pub fn as_deref(&self) -> Spec<&T::Target> - where - T: Deref, - { - Spec { x: &self.x, y: &self.y } - } - - /// Convert from `&mut Spec` to `Spec<&mut T>`. - pub fn as_mut(&mut self) -> Spec<&mut T> { - Spec { x: &mut self.x, y: &mut self.y } - } - - /// Zip two instances into an instance over a tuple. - pub fn zip(self, other: Spec) -> Spec<(T, U)> { - Spec { - x: (self.x, other.x), - y: (self.y, other.y), - } - } - - /// Whether a condition is true for at least one of fields. - pub fn any(self, mut f: F) -> bool - where - F: FnMut(&T) -> bool, - { - f(&self.x) || f(&self.y) - } - - /// Whether a condition is true for both fields. - pub fn all(self, mut f: F) -> bool - where - F: FnMut(&T) -> bool, - { - f(&self.x) && f(&self.y) - } - - /// Filter the individual fields with a mask. - pub fn filter(self, mask: Spec) -> Spec> { - Spec { - x: if mask.x { Some(self.x) } else { None }, - y: if mask.y { Some(self.y) } else { None }, - } - } - - /// Convert to the generic representation. - pub fn to_gen(self, main: SpecAxis) -> Gen { - match main { - SpecAxis::Horizontal => Gen::new(self.y, self.x), - SpecAxis::Vertical => Gen::new(self.x, self.y), - } - } -} - -impl Spec { - /// Create a new instance with y set to its default value. - pub fn with_x(x: T) -> Self { - Self { x, y: T::default() } - } - - /// Create a new instance with x set to its default value. - pub fn with_y(y: T) -> Self { - Self { x: T::default(), y } - } -} - -impl Spec { - /// The component-wise minimum of this and another instance. - pub fn min(self, other: Self) -> Self { - Self { - x: self.x.min(other.x), - y: self.y.min(other.y), - } - } - - /// The component-wise minimum of this and another instance. - pub fn max(self, other: Self) -> Self { - Self { - x: self.x.max(other.x), - y: self.y.max(other.y), - } - } -} - -impl Get for Spec { - type Component = T; - - fn get(self, axis: SpecAxis) -> T { - match axis { - SpecAxis::Horizontal => self.x, - SpecAxis::Vertical => self.y, - } - } - - fn get_mut(&mut self, axis: SpecAxis) -> &mut T { - match axis { - SpecAxis::Horizontal => &mut self.x, - SpecAxis::Vertical => &mut self.y, - } - } -} - -impl Debug for Spec -where - T: Debug + 'static, -{ - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if let Spec { x: Some(x), y: Some(y) } = - self.as_ref().map(|v| (v as &dyn Any).downcast_ref::()) - { - write!(f, "{:?}-{:?}", x, y) - } else if (&self.x as &dyn Any).is::() { - write!(f, "Size({:?}, {:?})", self.x, self.y) - } else { - write!(f, "Spec({:?}, {:?})", self.x, self.y) - } - } -} - -/// The two specific layouting axes. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub enum SpecAxis { - /// The horizontal layouting axis. - Horizontal, - /// The vertical layouting axis. - Vertical, -} - -impl SpecAxis { - /// The direction with the given positivity for this axis. - pub fn dir(self, positive: bool) -> Dir { - match (self, positive) { - (Self::Horizontal, true) => Dir::LTR, - (Self::Horizontal, false) => Dir::RTL, - (Self::Vertical, true) => Dir::TTB, - (Self::Vertical, false) => Dir::BTT, - } - } - - /// The other axis. - pub fn other(self) -> Self { - match self { - Self::Horizontal => Self::Vertical, - Self::Vertical => Self::Horizontal, - } - } -} - -impl Debug for SpecAxis { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::Horizontal => "horizontal", - Self::Vertical => "vertical", - }) - } -} - -/// A size in 2D. -pub type Size = Spec; - -impl Size { - /// The zero value. - pub const fn zero() -> Self { - Self { x: Length::zero(), y: Length::zero() } - } - - /// Whether the other size fits into this one (smaller width and height). - pub fn fits(self, other: Self) -> bool { - self.x.fits(other.x) && self.y.fits(other.y) - } - - /// Convert to a point. - pub fn to_point(self) -> Point { - Point::new(self.x, self.y) - } -} - -impl Numeric for Size { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.x.is_finite() && self.y.is_finite() - } -} - -impl Neg for Size { - type Output = Self; - - fn neg(self) -> Self { - Self { x: -self.x, y: -self.y } - } -} - -impl Add for Size { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self { x: self.x + other.x, y: self.y + other.y } - } -} - -sub_impl!(Size - Size -> Size); - -impl Mul for Size { - type Output = Self; - - fn mul(self, other: f64) -> Self { - Self { x: self.x * other, y: self.y * 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 { x: self.x / other, y: self.y / other } - } -} - -assign_impl!(Size -= Size); -assign_impl!(Size += Size); -assign_impl!(Size *= f64); -assign_impl!(Size /= f64); - -impl Spec> { - /// Whether the individual fields are some. - pub fn map_is_some(&self) -> Spec { - self.as_ref().map(Option::is_some) - } - - /// Whether the individual fields are none. - pub fn map_is_none(&self) -> Spec { - self.as_ref().map(Option::is_none) - } - - /// Unwrap the individual fields. - pub fn unwrap_or(self, other: Spec) -> Spec { - Spec { - x: self.x.unwrap_or(other.x), - y: self.y.unwrap_or(other.y), - } - } -} - -impl Spec { - /// Select `t.x` if `self.x` is true and `f.x` otherwise and same for `y`. - pub fn select(self, t: Spec, f: Spec) -> Spec { - Spec { - x: if self.x { t.x } else { f.x }, - y: if self.y { t.y } else { f.y }, - } - } -} - -impl Not for Spec { - type Output = Self; - - fn not(self) -> Self::Output { - Self { x: !self.x, y: !self.y } - } -} - -impl BitOr for Spec { - type Output = Self; - - fn bitor(self, rhs: Self) -> Self::Output { - Self { x: self.x | rhs.x, y: self.y | rhs.y } - } -} - -impl BitOr for Spec { - type Output = Self; - - fn bitor(self, rhs: bool) -> Self::Output { - Self { x: self.x | rhs, y: self.y | rhs } - } -} - -impl BitAnd for Spec { - type Output = Self; - - fn bitand(self, rhs: Self) -> Self::Output { - Self { x: self.x & rhs.x, y: self.y & rhs.y } - } -} - -impl BitAnd for Spec { - type Output = Self; - - fn bitand(self, rhs: bool) -> Self::Output { - Self { x: self.x & rhs, y: self.y & rhs } - } -} - -impl BitOrAssign for Spec { - fn bitor_assign(&mut self, rhs: Self) { - self.x |= rhs.x; - self.y |= rhs.y; - } -} - -impl BitAndAssign for Spec { - fn bitand_assign(&mut self, rhs: Self) { - self.x &= rhs.x; - self.y &= rhs.y; - } -} diff --git a/src/geom/transform.rs b/src/geom/transform.rs index b82807fd9..1ff1dfdd7 100644 --- a/src/geom/transform.rs +++ b/src/geom/transform.rs @@ -7,8 +7,8 @@ pub struct Transform { pub ky: Ratio, pub kx: Ratio, pub sy: Ratio, - pub tx: Length, - pub ty: Length, + pub tx: Abs, + pub ty: Abs, } impl Transform { @@ -19,13 +19,13 @@ impl Transform { ky: Ratio::zero(), kx: Ratio::zero(), sy: Ratio::one(), - tx: Length::zero(), - ty: Length::zero(), + tx: Abs::zero(), + ty: Abs::zero(), } } /// A translate transform. - pub const fn translate(tx: Length, ty: Length) -> Self { + pub const fn translate(tx: Abs, ty: Abs) -> Self { Self { tx, ty, ..Self::identity() } } diff --git a/src/library/graphics/image.rs b/src/library/graphics/image.rs index 7523471da..343b4788c 100644 --- a/src/library/graphics/image.rs +++ b/src/library/graphics/image.rs @@ -33,7 +33,7 @@ impl ImageNode { let height = args.named("height")?; Ok(Content::inline( - ImageNode(image).pack().sized(Spec::new(width, height)), + ImageNode(image).pack().sized(Axes::new(width, height)), )) } } @@ -62,7 +62,7 @@ impl Layout for ImageNode { } else if first.y.is_finite() { Size::new(first.x.min(first.y * px_ratio), first.y) } else { - Size::new(Length::pt(pxw), Length::pt(pxh)) + Size::new(Abs::pt(pxw), Abs::pt(pxh)) }; // Compute the actual size of the fitted image. diff --git a/src/library/graphics/line.rs b/src/library/graphics/line.rs index 192f83507..78878014e 100644 --- a/src/library/graphics/line.rs +++ b/src/library/graphics/line.rs @@ -4,9 +4,9 @@ use crate::library::prelude::*; #[derive(Debug, Hash)] pub struct LineNode { /// Where the line starts. - origin: Spec>, + origin: Axes>, /// The offset from the `origin` where the line ends. - delta: Spec>, + delta: Axes>, } #[node] @@ -18,18 +18,17 @@ impl LineNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let origin = args.named("origin")?.unwrap_or_default(); - let delta = match args.named::>>("to")? { + let delta = match args.named::>>("to")? { Some(to) => to.zip(origin).map(|(to, from)| to - from), None => { - let length = args - .named::>("length")? - .unwrap_or(Length::cm(1.0).into()); + let length = + args.named::>("length")?.unwrap_or(Abs::cm(1.0).into()); let angle = args.named::("angle")?.unwrap_or_default(); let x = angle.cos() * length; let y = angle.sin() * length; - Spec::new(x, y) + Axes::new(x, y) } }; @@ -69,12 +68,12 @@ impl Layout for LineNode { } castable! { - Spec>, + Axes>, Expected: "array of two relative lengths", Value::Array(array) => { let mut iter = array.into_iter(); match (iter.next(), iter.next(), iter.next()) { - (Some(a), Some(b), None) => Spec::new(a.cast()?, b.cast()?), + (Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?), _ => Err("point array must contain exactly two entries")?, } }, diff --git a/src/library/graphics/shape.rs b/src/library/graphics/shape.rs index eea025686..7a7421093 100644 --- a/src/library/graphics/shape.rs +++ b/src/library/graphics/shape.rs @@ -29,20 +29,19 @@ impl ShapeNode { /// How much to pad the shape's content. #[property(resolve, fold)] - pub const INSET: Sides>> = Sides::splat(Relative::zero()); + pub const INSET: Sides>> = Sides::splat(Rel::zero()); /// How much to extend the shape's dimensions beyond the allocated space. #[property(resolve, fold)] - pub const OUTSET: Sides>> = Sides::splat(Relative::zero()); + pub const OUTSET: Sides>> = Sides::splat(Rel::zero()); /// How much to round the shape's corners. #[property(skip, resolve, fold)] - pub const RADIUS: Corners>> = - Corners::splat(Relative::zero()); + pub const RADIUS: Corners>> = Corners::splat(Rel::zero()); fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let size = match S { - SQUARE => args.named::("size")?.map(Relative::from), - CIRCLE => args.named::("radius")?.map(|r| 2.0 * Relative::from(r)), + SQUARE => args.named::("size")?.map(Rel::from), + CIRCLE => args.named::("radius")?.map(|r| 2.0 * Rel::from(r)), _ => None, }; @@ -57,7 +56,7 @@ impl ShapeNode { }; Ok(Content::inline( - Self(args.eat()?).pack().sized(Spec::new(width, height)), + Self(args.eat()?).pack().sized(Axes::new(width, height)), )) } @@ -90,7 +89,7 @@ impl Layout for ShapeNode { } // Pad the child. - let child = child.clone().padded(inset.map(|side| side.map(RawLength::from))); + let child = child.clone().padded(inset.map(|side| side.map(Length::from))); let mut pod = Regions::one(regions.first, regions.base, regions.expand); frames = child.layout(world, &pod, styles)?; @@ -112,14 +111,13 @@ impl Layout for ShapeNode { }; pod.first = Size::splat(length); - pod.expand = Spec::splat(true); + pod.expand = Axes::splat(true); frames = child.layout(world, &pod, styles)?; } } else { // The default size that a shape takes on if it has no child and // enough space. - let mut size = - Size::new(Length::pt(45.0), Length::pt(30.0)).min(regions.first); + let mut size = Size::new(Abs::pt(45.0), Abs::pt(30.0)).min(regions.first); if is_quadratic(S) { let length = if regions.expand.x || regions.expand.y { @@ -159,16 +157,11 @@ impl Layout for ShapeNode { if fill.is_some() || stroke.iter().any(Option::is_some) { if is_round(S) { - let shape = Shape { - geometry: Geometry::Ellipse(size), - fill, - stroke: stroke.left, - }; + let shape = ellipse(size, fill, stroke.left); frame.prepend(pos, Element::Shape(shape)); } else { frame.prepend_multiple( - RoundedRect::new(size, radius) - .shapes(fill, stroke) + rounded_rect(size, radius, fill, stroke) .into_iter() .map(|x| (pos, Element::Shape(x))), ) diff --git a/src/library/layout/align.rs b/src/library/layout/align.rs index 705d555bd..95f5c01f9 100644 --- a/src/library/layout/align.rs +++ b/src/library/layout/align.rs @@ -5,7 +5,7 @@ use crate::library::text::{HorizontalAlign, ParNode}; #[derive(Debug, Hash)] pub struct AlignNode { /// How to align the node horizontally and vertically. - pub aligns: Spec>, + pub aligns: Axes>, /// The node to be aligned. pub child: LayoutNode, } @@ -13,11 +13,11 @@ pub struct AlignNode { #[node] impl AlignNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - let aligns: Spec> = args.find()?.unwrap_or_default(); + let aligns: Axes> = args.find()?.unwrap_or_default(); let body: Content = args.expect("body")?; Ok(match (body, aligns) { (Content::Block(node), _) => Content::Block(node.aligned(aligns)), - (other, Spec { x: Some(x), y: None }) => { + (other, Axes { x: Some(x), y: None }) => { other.styled(ParNode::ALIGN, HorizontalAlign(x)) } (other, _) => Content::Block(other.pack().aligned(aligns)), @@ -34,7 +34,7 @@ impl Layout for AlignNode { ) -> SourceResult> { // The child only needs to expand along an axis if there's no alignment. let mut pod = regions.clone(); - pod.expand &= self.aligns.map_is_none(); + pod.expand &= self.aligns.as_ref().map(Option::is_none); // Align paragraphs inside the child. let mut passed = StyleMap::new(); @@ -51,7 +51,7 @@ impl Layout for AlignNode { let aligns = self .aligns .map(|align| align.resolve(styles)) - .unwrap_or(Spec::new(Align::Left, Align::Top)); + .unwrap_or(Axes::new(Align::Left, Align::Top)); frame.resize(target, aligns); } diff --git a/src/library/layout/columns.rs b/src/library/layout/columns.rs index 8ae4394e4..3ba3598ca 100644 --- a/src/library/layout/columns.rs +++ b/src/library/layout/columns.rs @@ -15,7 +15,7 @@ pub struct ColumnsNode { impl ColumnsNode { /// The size of the gutter space between each column. #[property(resolve)] - pub const GUTTER: Relative = Ratio::new(0.04).into(); + pub const GUTTER: Rel = Ratio::new(0.04).into(); fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Content::block(Self { @@ -53,7 +53,7 @@ impl Layout for ColumnsNode { .skip(1) .collect(), last: regions.last, - expand: Spec::new(true, regions.expand.y), + expand: Axes::new(true, regions.expand.y), }; // Layout the children. @@ -69,9 +69,9 @@ impl Layout for ColumnsNode { // Otherwise its the maximum column height for the frame. In that // case, the frame is first created with zero height and then // resized. - let height = if regions.expand.y { region.y } else { Length::zero() }; + let height = if regions.expand.y { region.y } else { Abs::zero() }; let mut output = Frame::new(Size::new(regions.first.x, height)); - let mut cursor = Length::zero(); + let mut cursor = Abs::zero(); for _ in 0 .. columns { let frame = match frames.next() { diff --git a/src/library/layout/container.rs b/src/library/layout/container.rs index 23556a2e0..9b1f8f568 100644 --- a/src/library/layout/container.rs +++ b/src/library/layout/container.rs @@ -9,7 +9,7 @@ impl BoxNode { let width = args.named("width")?; let height = args.named("height")?; let body: LayoutNode = args.eat()?.unwrap_or_default(); - Ok(Content::inline(body.sized(Spec::new(width, height)))) + Ok(Content::inline(body.sized(Axes::new(width, height)))) } } diff --git a/src/library/layout/flow.rs b/src/library/layout/flow.rs index 7cb529107..1f0a2b4ae 100644 --- a/src/library/layout/flow.rs +++ b/src/library/layout/flow.rs @@ -81,14 +81,14 @@ pub struct FlowLayouter { /// The regions to layout children into. regions: Regions, /// Whether the flow should expand to fill the region. - expand: Spec, + expand: Axes, /// The full size of `regions.size` that was available before we started /// subtracting. full: Size, /// The size used by the frames for the current region. used: Size, /// The sum of fractions in the current region. - fr: Fraction, + fr: Fr, /// Spacing and layouted nodes. items: Vec, /// Finished frames for previous regions. @@ -98,11 +98,11 @@ pub struct FlowLayouter { /// A prepared item in a flow layout. enum FlowItem { /// Absolute spacing between other items. - Absolute(Length), + Absolute(Abs), /// Fractional spacing between other items. - Fractional(Fraction), + Fractional(Fr), /// A frame for a layouted child node and how to align it. - Frame(Frame, Spec), + Frame(Frame, Axes), /// An absolutely placed frame. Placed(Frame), } @@ -122,7 +122,7 @@ impl FlowLayouter { expand, full, used: Size::zero(), - fr: Fraction::zero(), + fr: Fr::zero(), items: vec![], finished: vec![], } @@ -169,7 +169,7 @@ impl FlowLayouter { } // How to align the node. - let aligns = Spec::new( + let aligns = Axes::new( // For non-expanding paragraphs it is crucial that we align the // whole paragraph as it is itself aligned. styles.get(ParNode::ALIGN), @@ -215,7 +215,7 @@ impl FlowLayouter { } let mut output = Frame::new(size); - let mut offset = Length::zero(); + let mut offset = Abs::zero(); let mut ruler = Align::Top; // Place all frames. @@ -245,7 +245,7 @@ impl FlowLayouter { self.regions.next(); self.full = self.regions.first; self.used = Size::zero(); - self.fr = Fraction::zero(); + self.fr = Fr::zero(); self.finished.push(output); } diff --git a/src/library/layout/grid.rs b/src/library/layout/grid.rs index 2c246df98..a1098c6d1 100644 --- a/src/library/layout/grid.rs +++ b/src/library/layout/grid.rs @@ -4,9 +4,9 @@ use crate::library::prelude::*; #[derive(Debug, Hash)] pub struct GridNode { /// Defines sizing for content rows and columns. - pub tracks: Spec>, + pub tracks: Axes>, /// Defines sizing of gutter rows and columns between content. - pub gutter: Spec>, + pub gutter: Axes>, /// The nodes to be arranged in a grid. pub cells: Vec, } @@ -20,8 +20,8 @@ impl GridNode { let column_gutter = args.named("column-gutter")?; let row_gutter = args.named("row-gutter")?; Ok(Content::block(Self { - tracks: Spec::new(columns, rows), - gutter: Spec::new( + tracks: Axes::new(columns, rows), + gutter: Axes::new( column_gutter.unwrap_or_else(|| base_gutter.clone()), row_gutter.unwrap_or(base_gutter), ), @@ -59,10 +59,10 @@ pub enum TrackSizing { Auto, /// A track size specified in absolute terms and relative to the parent's /// size. - Relative(Relative), + Relative(Rel), /// A track size specified as a fraction of the remaining free space in the /// parent. - Fractional(Fraction), + Fractional(Fr), } castable! { @@ -105,16 +105,16 @@ pub struct GridLayouter<'a> { /// The inherited styles. styles: StyleChain<'a>, /// Resolved column sizes. - rcols: Vec, + rcols: Vec, /// Rows in the current region. lrows: Vec, /// The full height of the current region. - full: Length, + full: Abs, /// The used-up size of the current region. The horizontal size is /// determined once after columns are resolved and not touched again. used: Size, /// The sum of fractions in the current region. - fr: Fraction, + fr: Fr, /// Frames for finished regions. finished: Vec, } @@ -125,7 +125,7 @@ enum Row { /// Finished row frame of auto or relative row. Frame(Frame), /// Fractional row with y index. - Fr(Fraction, usize), + Fr(Fr, usize), } impl<'a> GridLayouter<'a> { @@ -134,8 +134,8 @@ impl<'a> GridLayouter<'a> { /// This prepares grid layout by unifying content and gutter tracks. pub fn new( world: Tracked<'a, dyn World>, - tracks: Spec<&[TrackSizing]>, - gutter: Spec<&[TrackSizing]>, + tracks: Axes<&[TrackSizing]>, + gutter: Axes<&[TrackSizing]>, cells: &'a [LayoutNode], regions: &Regions, styles: StyleChain<'a>, @@ -156,7 +156,7 @@ impl<'a> GridLayouter<'a> { }; let auto = TrackSizing::Auto; - let zero = TrackSizing::Relative(Relative::zero()); + let zero = TrackSizing::Relative(Rel::zero()); let get_or = |tracks: &[_], idx, default| { tracks.get(idx).or(tracks.last()).copied().unwrap_or(default) }; @@ -178,13 +178,13 @@ impl<'a> GridLayouter<'a> { rows.pop(); let full = regions.first.y; - let rcols = vec![Length::zero(); cols.len()]; + let rcols = vec![Abs::zero(); cols.len()]; let lrows = vec![]; // We use the regions for auto row measurement. Since at that moment, // columns are already sized, we can enable horizontal expansion. let mut regions = regions.clone(); - regions.expand = Spec::new(true, false); + regions.expand = Axes::new(true, false); Self { world, @@ -197,7 +197,7 @@ impl<'a> GridLayouter<'a> { lrows, full, used: Size::zero(), - fr: Fraction::zero(), + fr: Fr::zero(), finished: vec![], } } @@ -230,10 +230,10 @@ impl<'a> GridLayouter<'a> { /// Determine all column sizes. fn measure_columns(&mut self) -> SourceResult<()> { // Sum of sizes of resolved relative tracks. - let mut rel = Length::zero(); + let mut rel = Abs::zero(); // Sum of fractions of all fractional tracks. - let mut fr = Fraction::zero(); + let mut fr = Fr::zero(); // Resolve the size of all relative columns and compute the sum of all // fractional tracks. @@ -252,14 +252,14 @@ impl<'a> GridLayouter<'a> { // Size that is not used by fixed-size columns. let available = self.regions.first.x - rel; - if available >= Length::zero() { + if available >= Abs::zero() { // Determine size of auto columns. let (auto, count) = self.measure_auto_columns(available)?; // If there is remaining space, distribute it to fractional columns, // otherwise shrink auto columns. let remaining = available - auto; - if remaining >= Length::zero() { + if remaining >= Abs::zero() { if !fr.is_zero() { self.grow_fractional_columns(remaining, fr); } @@ -275,11 +275,8 @@ impl<'a> GridLayouter<'a> { } /// Measure the size that is available to auto columns. - fn measure_auto_columns( - &mut self, - available: Length, - ) -> SourceResult<(Length, usize)> { - let mut auto = Length::zero(); + fn measure_auto_columns(&mut self, available: Abs) -> SourceResult<(Abs, usize)> { + let mut auto = Abs::zero(); let mut count = 0; // Determine size of auto columns by laying out all cells in those @@ -289,12 +286,12 @@ impl<'a> GridLayouter<'a> { continue; } - let mut resolved = Length::zero(); + let mut resolved = Abs::zero(); for y in 0 .. self.rows.len() { if let Some(node) = self.cell(x, y) { let size = Size::new(available, self.regions.base.y); let mut pod = - Regions::one(size, self.regions.base, Spec::splat(false)); + Regions::one(size, self.regions.base, Axes::splat(false)); // For relative rows, we can already resolve the correct // base, for auto it's already correct and for fr we could @@ -318,7 +315,7 @@ impl<'a> GridLayouter<'a> { } /// Distribute remaining space to fractional columns. - fn grow_fractional_columns(&mut self, remaining: Length, fr: Fraction) { + fn grow_fractional_columns(&mut self, remaining: Abs, fr: Fr) { for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { if let TrackSizing::Fractional(v) = col { *rcol = v.share(fr, remaining); @@ -327,7 +324,7 @@ impl<'a> GridLayouter<'a> { } /// Redistribute space to auto columns so that each gets a fair share. - fn shrink_auto_columns(&mut self, available: Length, count: usize) { + fn shrink_auto_columns(&mut self, available: Abs, count: usize) { // The fair share each auto column may have. let fair = available / count as f64; @@ -359,7 +356,7 @@ impl<'a> GridLayouter<'a> { /// Layout a row with automatic height. Such a row may break across multiple /// regions. fn layout_auto_row(&mut self, y: usize) -> SourceResult<()> { - let mut resolved: Vec = vec![]; + let mut resolved: Vec = vec![]; // Determine the size for each region of the row. for (x, &rcol) in self.rcols.iter().enumerate() { @@ -426,11 +423,7 @@ impl<'a> GridLayouter<'a> { /// Layout a row with relative height. Such a row cannot break across /// multiple regions, but it may force a region break. - fn layout_relative_row( - &mut self, - v: Relative, - y: usize, - ) -> SourceResult<()> { + fn layout_relative_row(&mut self, v: Rel, y: usize) -> SourceResult<()> { let resolved = v.resolve(self.styles).relative_to(self.regions.base.y); let frame = self.layout_single_row(resolved, y)?; @@ -451,7 +444,7 @@ impl<'a> GridLayouter<'a> { } /// Layout a row with fixed height and return its frame. - fn layout_single_row(&mut self, height: Length, y: usize) -> SourceResult { + fn layout_single_row(&mut self, height: Abs, y: usize) -> SourceResult { let mut output = Frame::new(Size::new(self.used.x, height)); let mut pos = Point::zero(); @@ -462,11 +455,11 @@ impl<'a> GridLayouter<'a> { // Set the base to the region's base for auto rows and to the // size for relative and fractional rows. - let base = Spec::new(self.cols[x], self.rows[y]) + let base = Axes::new(self.cols[x], self.rows[y]) .map(|s| s == TrackSizing::Auto) .select(self.regions.base, size); - let pod = Regions::one(size, base, Spec::splat(true)); + let pod = Regions::one(size, base, Axes::splat(true)); let frame = node.layout(self.world, &pod, self.styles)?.remove(0); match frame.role() { Some(Role::ListLabel | Role::ListItemBody) => { @@ -488,7 +481,7 @@ impl<'a> GridLayouter<'a> { /// Layout a row spanning multiple regions. fn layout_multi_row( &mut self, - heights: &[Length], + heights: &[Abs], y: usize, ) -> SourceResult> { // Prepare frames. @@ -499,7 +492,7 @@ impl<'a> GridLayouter<'a> { // Prepare regions. let size = Size::new(self.used.x, heights[0]); - let mut pod = Regions::one(size, self.regions.base, Spec::splat(true)); + let mut pod = Regions::one(size, self.regions.base, Axes::splat(true)); pod.backlog = heights[1 ..].to_vec(); // Layout the row. @@ -573,8 +566,8 @@ impl<'a> GridLayouter<'a> { self.finished.push(output); self.regions.next(); self.full = self.regions.first.y; - self.used.y = Length::zero(); - self.fr = Fraction::zero(); + self.used.y = Abs::zero(); + self.fr = Fr::zero(); Ok(()) } diff --git a/src/library/layout/pad.rs b/src/library/layout/pad.rs index 06c3672f2..b0238d403 100644 --- a/src/library/layout/pad.rs +++ b/src/library/layout/pad.rs @@ -4,7 +4,7 @@ use crate::library::prelude::*; #[derive(Debug, Hash)] pub struct PadNode { /// The amount of padding. - pub padding: Sides>, + pub padding: Sides>, /// The child node whose sides to pad. pub child: LayoutNode, } @@ -54,7 +54,7 @@ impl Layout for PadNode { } /// Shrink a size by padding relative to the size itself. -fn shrink(size: Size, padding: Sides>) -> Size { +fn shrink(size: Size, padding: Sides>) -> Size { size - padding.relative_to(size).sum_by_axis() } @@ -77,7 +77,7 @@ fn shrink(size: Size, padding: Sides>) -> Size { /// <=> w - p.rel * w - p.abs = s /// <=> (1 - p.rel) * w = s + p.abs /// <=> w = (s + p.abs) / (1 - p.rel) -fn grow(size: Size, padding: Sides>) -> Size { +fn grow(size: Size, padding: Sides>) -> Size { size.zip(padding.sum_by_axis()) .map(|(s, p)| (s + p.abs).safe_div(1.0 - p.rel.get())) } diff --git a/src/library/layout/page.rs b/src/library/layout/page.rs index 7d13163da..2e5cf2f97 100644 --- a/src/library/layout/page.rs +++ b/src/library/layout/page.rs @@ -11,17 +11,16 @@ pub struct PageNode(pub LayoutNode); impl PageNode { /// The unflipped width of the page. #[property(resolve)] - pub const WIDTH: Smart = Smart::Custom(Paper::A4.width().into()); + pub const WIDTH: Smart = Smart::Custom(Paper::A4.width().into()); /// The unflipped height of the page. #[property(resolve)] - pub const HEIGHT: Smart = Smart::Custom(Paper::A4.height().into()); + pub const HEIGHT: Smart = Smart::Custom(Paper::A4.height().into()); /// Whether the page is flipped into landscape orientation. pub const FLIPPED: bool = false; /// The page's margins. #[property(fold)] - pub const MARGINS: Sides>>> = - Sides::splat(Smart::Auto); + pub const MARGINS: Sides>>> = Sides::splat(Smart::Auto); /// How many columns the page has. pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap(); @@ -63,8 +62,8 @@ impl PageNode { ) -> SourceResult> { // When one of the lengths is infinite the page fits its content along // that axis. - let width = styles.get(Self::WIDTH).unwrap_or(Length::inf()); - let height = styles.get(Self::HEIGHT).unwrap_or(Length::inf()); + let width = styles.get(Self::WIDTH).unwrap_or(Abs::inf()); + let height = styles.get(Self::HEIGHT).unwrap_or(Abs::inf()); let mut size = Size::new(width, height); if styles.get(Self::FLIPPED) { std::mem::swap(&mut size.x, &mut size.y); @@ -76,7 +75,7 @@ impl PageNode { } // Determine the margins. - let default = Relative::from(0.1190 * min); + let default = Rel::from(0.1190 * min); let padding = styles.get(Self::MARGINS).map(|side| side.unwrap_or(default)); let mut child = self.0.clone(); @@ -96,7 +95,7 @@ impl PageNode { } // Layout the child. - let regions = Regions::repeat(size, size, size.map(Length::is_finite)); + let regions = Regions::repeat(size, size, size.map(Abs::is_finite)); let mut frames = child.layout(world, ®ions, styles)?; let header = styles.get(Self::HEADER); @@ -127,7 +126,7 @@ impl PageNode { (Role::Background, background, Point::zero(), size), ] { if let Some(content) = marginal.resolve(world, page)? { - let pod = Regions::one(area, area, Spec::splat(true)); + let pod = Regions::one(area, area, Axes::splat(true)); let mut sub = content.layout(world, &pod, styles)?.remove(0); sub.apply_role(role); @@ -224,13 +223,13 @@ pub struct Paper { impl Paper { /// The width of the paper. - pub fn width(self) -> Length { - Length::mm(self.width) + pub fn width(self) -> Abs { + Abs::mm(self.width) } /// The height of the paper. - pub fn height(self) -> Length { - Length::mm(self.height) + pub fn height(self) -> Abs { + Abs::mm(self.height) } } diff --git a/src/library/layout/place.rs b/src/library/layout/place.rs index 01da62e5c..8b68c0875 100644 --- a/src/library/layout/place.rs +++ b/src/library/layout/place.rs @@ -8,12 +8,12 @@ pub struct PlaceNode(pub LayoutNode); #[node] impl PlaceNode { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { - let aligns = args.find()?.unwrap_or(Spec::with_x(Some(RawAlign::Start))); + let aligns = args.find()?.unwrap_or(Axes::with_x(Some(RawAlign::Start))); let dx = args.named("dx")?.unwrap_or_default(); let dy = args.named("dy")?.unwrap_or_default(); let body: LayoutNode = args.expect("body")?; Ok(Content::block(Self( - body.moved(Spec::new(dx, dy)).aligned(aligns), + body.moved(Axes::new(dx, dy)).aligned(aligns), ))) } } @@ -30,7 +30,7 @@ impl Layout for PlaceNode { // The pod is the base area of the region because for absolute // placement we don't really care about the already used area. let pod = { - let finite = regions.base.map(Length::is_finite); + let finite = regions.base.map(Abs::is_finite); let expand = finite & (regions.expand | out_of_flow); Regions::one(regions.base, regions.base, expand) }; diff --git a/src/library/layout/spacing.rs b/src/library/layout/spacing.rs index 0c5cbb92d..28e52d73b 100644 --- a/src/library/layout/spacing.rs +++ b/src/library/layout/spacing.rs @@ -31,10 +31,10 @@ impl VNode { #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Spacing { /// Spacing specified in absolute terms and relative to the parent's size. - Relative(Relative), + Relative(Rel), /// Spacing specified as a fraction of the remaining free space in the /// parent. - Fractional(Fraction), + Fractional(Fr), } impl Spacing { @@ -44,9 +44,9 @@ impl Spacing { } } -impl From for Spacing { - fn from(length: Length) -> Self { - Self::Relative(length.into()) +impl From for Spacing { + fn from(abs: Abs) -> Self { + Self::Relative(abs.into()) } } @@ -71,12 +71,12 @@ castable! { /// Spacing around and between block-level nodes, relative to paragraph spacing. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct BlockSpacing(Relative); +pub struct BlockSpacing(Rel); -castable!(BlockSpacing: Relative); +castable!(BlockSpacing: Rel); impl Resolve for BlockSpacing { - type Output = Length; + type Output = Abs; fn resolve(self, styles: StyleChain) -> Self::Output { let whole = styles.get(ParNode::SPACING); diff --git a/src/library/layout/stack.rs b/src/library/layout/stack.rs index b9a26642b..b9663dd66 100644 --- a/src/library/layout/stack.rs +++ b/src/library/layout/stack.rs @@ -90,19 +90,19 @@ pub struct StackLayouter<'a> { /// The stacking direction. dir: Dir, /// The axis of the stacking direction. - axis: SpecAxis, + axis: Axis, /// The regions to layout children into. regions: Regions, /// The inherited styles. styles: StyleChain<'a>, /// Whether the stack itself should expand to fill the region. - expand: Spec, + expand: Axes, /// The full size of the current region that was available at the start. full: Size, /// The generic size used by the frames for the current region. - used: Gen, + used: Gen, /// The sum of fractions in the current region. - fr: Fraction, + fr: Fr, /// Already layouted items whose exact positions are not yet known due to /// fractional spacing. items: Vec, @@ -113,9 +113,9 @@ pub struct StackLayouter<'a> { /// A prepared item in a stack layout. enum StackItem { /// Absolute spacing between other items. - Absolute(Length), + Absolute(Abs), /// Fractional spacing between other items. - Fractional(Fraction), + Fractional(Fr), /// A frame for a layouted child node. Frame(Frame, Align), } @@ -139,7 +139,7 @@ impl<'a> StackLayouter<'a> { expand, full, used: Gen::zero(), - fr: Fraction::zero(), + fr: Fr::zero(), items: vec![], finished: vec![], } @@ -200,7 +200,12 @@ impl<'a> StackLayouter<'a> { frame.apply_role(Role::GenericBlock); // Grow our size, shrink the region and save the frame for later. - let size = frame.size().to_gen(self.axis); + let size = frame.size(); + let size = match self.axis { + Axis::X => Gen::new(size.y, size.x), + Axis::Y => Gen::new(size.x, size.y), + }; + self.used.main += size.main; self.used.cross.set_max(size.cross); *self.regions.first.get_mut(self.axis) -= size.main; @@ -218,7 +223,7 @@ impl<'a> StackLayouter<'a> { pub fn finish_region(&mut self) { // Determine the size of the stack in this region dependening on whether // the region expands. - let used = self.used.to_spec(self.axis); + let used = self.used.to_axes(self.axis); let mut size = self.expand.select(self.full, used); // Expand fully if there are fr spacings. @@ -230,7 +235,7 @@ impl<'a> StackLayouter<'a> { } let mut output = Frame::new(size); - let mut cursor = Length::zero(); + let mut cursor = Abs::zero(); let mut ruler: Align = self.dir.start().into(); // Place all frames. @@ -255,7 +260,7 @@ impl<'a> StackLayouter<'a> { self.used.main - child - cursor }; - let pos = Gen::new(Length::zero(), block).to_point(self.axis); + let pos = Gen::new(Abs::zero(), block).to_point(self.axis); cursor += child; output.push_frame(pos, frame); } @@ -266,7 +271,7 @@ impl<'a> StackLayouter<'a> { self.regions.next(); self.full = self.regions.first; self.used = Gen::zero(); - self.fr = Fraction::zero(); + self.fr = Fr::zero(); self.finished.push(output); } @@ -276,3 +281,39 @@ impl<'a> StackLayouter<'a> { self.finished } } + +/// A container with a main and cross component. +#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] +pub struct Gen { + /// The main component. + pub cross: T, + /// The cross component. + pub main: T, +} + +impl Gen { + /// Create a new instance from the two components. + pub const fn new(cross: T, main: T) -> Self { + Self { cross, main } + } + + /// Convert to the specific representation, given the current main axis. + pub fn to_axes(self, main: Axis) -> Axes { + match main { + Axis::X => Axes::new(self.main, self.cross), + Axis::Y => Axes::new(self.cross, self.main), + } + } +} + +impl Gen { + /// The zero value. + pub fn zero() -> Self { + Self { cross: Abs::zero(), main: Abs::zero() } + } + + /// Convert to a point. + pub fn to_point(self, main: Axis) -> Point { + self.to_axes(main).to_point() + } +} diff --git a/src/library/layout/transform.rs b/src/library/layout/transform.rs index b110f343a..ff42744a4 100644 --- a/src/library/layout/transform.rs +++ b/src/library/layout/transform.rs @@ -5,7 +5,7 @@ use crate::library::prelude::*; #[derive(Debug, Hash)] pub struct MoveNode { /// The offset by which to move the node. - pub delta: Spec>, + pub delta: Axes>, /// The node whose contents should be moved. pub child: LayoutNode, } @@ -16,7 +16,7 @@ impl MoveNode { let dx = args.named("dx")?.unwrap_or_default(); let dy = args.named("dy")?.unwrap_or_default(); Ok(Content::inline(Self { - delta: Spec::new(dx, dy), + delta: Axes::new(dx, dy), child: args.expect("body")?, })) } @@ -60,7 +60,7 @@ pub type ScaleNode = TransformNode; impl TransformNode { /// The origin of the transformation. #[property(resolve)] - pub const ORIGIN: Spec> = Spec::default(); + pub const ORIGIN: Axes> = Axes::default(); fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let transform = match T { @@ -94,7 +94,7 @@ impl Layout for TransformNode { let mut frames = self.child.layout(world, regions, styles)?; for frame in &mut frames { - let Spec { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s)); + let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s)); let transform = Transform::translate(x, y) .pre_concat(self.transform) .pre_concat(Transform::translate(-x, -y)); diff --git a/src/library/math/mod.rs b/src/library/math/mod.rs index 7d0fecb42..df00cb716 100644 --- a/src/library/math/mod.rs +++ b/src/library/math/mod.rs @@ -92,7 +92,7 @@ impl Show for MathNode { Ok(if self.display() { Content::block( LayoutNode::new(self.clone()) - .aligned(Spec::with_x(Some(Align::Center.into()))), + .aligned(Axes::with_x(Some(Align::Center.into()))), ) } else { Content::inline(self.clone()) @@ -171,9 +171,9 @@ fn layout_tex( // Determine the metrics. let (x0, y0, x1, y1) = renderer.size(&layout); - let width = Length::pt(x1 - x0); - let mut top = Length::pt(y1); - let mut bottom = Length::pt(-y0); + let width = Abs::pt(x1 - x0); + let mut top = Abs::pt(y1); + let mut bottom = Abs::pt(-y0); if style != Style::Display { let metrics = font.metrics(); top = styles.get(TextNode::TOP_EDGE).resolve(styles, metrics); @@ -204,7 +204,7 @@ fn layout_tex( /// A ReX rendering backend that renders into a frame. struct FrameBackend { frame: Frame, - baseline: Length, + baseline: Abs, font: Font, fill: Paint, lang: Lang, @@ -222,7 +222,7 @@ impl FrameBackend { /// Convert a cursor to a point. fn transform(&self, cursor: Cursor) -> Point { - Point::new(Length::pt(cursor.x), self.baseline + Length::pt(cursor.y)) + Point::new(Abs::pt(cursor.x), self.baseline + Abs::pt(cursor.y)) } } @@ -232,7 +232,7 @@ impl Backend for FrameBackend { self.transform(pos), Element::Text(Text { font: self.font.clone(), - size: Length::pt(scale), + size: Abs::pt(scale), fill: self.fill(), lang: self.lang, glyphs: vec![Glyph { @@ -249,10 +249,7 @@ impl Backend for FrameBackend { self.frame.push( self.transform(pos), Element::Shape(Shape { - geometry: Geometry::Rect(Size::new( - Length::pt(width), - Length::pt(height), - )), + geometry: Geometry::Rect(Size::new(Abs::pt(width), Abs::pt(height))), fill: Some(self.fill()), stroke: None, }), diff --git a/src/library/mod.rs b/src/library/mod.rs index 1691c8c76..b5a0e8ebc 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -218,17 +218,17 @@ impl StyleMapExt for StyleMap { /// Additional methods for layout nodes. pub trait LayoutNodeExt { /// Set alignments for this node. - fn aligned(self, aligns: Spec>) -> Self; + fn aligned(self, aligns: Axes>) -> Self; /// Pad this node at the sides. - fn padded(self, padding: Sides>) -> Self; + fn padded(self, padding: Sides>) -> Self; /// Transform this node's contents without affecting layout. - fn moved(self, delta: Spec>) -> Self; + fn moved(self, delta: Axes>) -> Self; } impl LayoutNodeExt for LayoutNode { - fn aligned(self, aligns: Spec>) -> Self { + fn aligned(self, aligns: Axes>) -> Self { if aligns.any(Option::is_some) { layout::AlignNode { aligns, child: self }.pack() } else { @@ -236,7 +236,7 @@ impl LayoutNodeExt for LayoutNode { } } - fn padded(self, padding: Sides>) -> Self { + fn padded(self, padding: Sides>) -> Self { if !padding.left.is_zero() || !padding.top.is_zero() || !padding.right.is_zero() @@ -248,7 +248,7 @@ impl LayoutNodeExt for LayoutNode { } } - fn moved(self, delta: Spec>) -> Self { + fn moved(self, delta: Axes>) -> Self { if delta.any(|r| !r.is_zero()) { layout::MoveNode { delta, child: self }.pack() } else { diff --git a/src/library/prelude.rs b/src/library/prelude.rs index 1d98e2a92..c5a6bc8e0 100644 --- a/src/library/prelude.rs +++ b/src/library/prelude.rs @@ -16,12 +16,9 @@ pub use crate::diag::{ pub use crate::frame::*; pub use crate::geom::*; pub use crate::model::{ - Arg, Args, Array, Cast, Dict, Dynamic, Func, Node, RawAlign, RawLength, RawStroke, - Scope, Smart, Str, Value, Vm, -}; -pub use crate::model::{ - Content, Fold, Key, Layout, LayoutNode, Regions, Resolve, Selector, Show, ShowNode, - StyleChain, StyleMap, StyleVec, + Arg, Args, Array, Cast, Content, Dict, Dynamic, Fold, Func, Key, Layout, LayoutNode, + Node, RawAlign, RawStroke, Regions, Resolve, Scope, Selector, Show, ShowNode, Smart, + Str, StyleChain, StyleMap, StyleVec, Value, Vm, }; pub use crate::syntax::{Span, Spanned}; pub use crate::util::EcoString; diff --git a/src/library/structure/list.rs b/src/library/structure/list.rs index 9d78238d7..4ccdbc544 100644 --- a/src/library/structure/list.rs +++ b/src/library/structure/list.rs @@ -29,10 +29,10 @@ impl ListNode { pub const LABEL: Label = Label::Default; /// The indentation of each item's label. #[property(resolve)] - pub const INDENT: RawLength = RawLength::zero(); + pub const INDENT: Length = Length::zero(); /// The space between the label and the body of each item. #[property(resolve)] - pub const BODY_INDENT: RawLength = Em::new(match L { + pub const BODY_INDENT: Length = Em::new(match L { LIST | ENUM => 0.5, DESC | _ => 1.0, }) @@ -159,13 +159,13 @@ impl Show for ListNode { } Ok(Content::block(GridNode { - tracks: Spec::with_x(vec![ + tracks: Axes::with_x(vec![ TrackSizing::Relative(indent.into()), TrackSizing::Auto, TrackSizing::Relative(body_indent.into()), TrackSizing::Auto, ]), - gutter: Spec::with_y(vec![TrackSizing::Relative(gutter.into())]), + gutter: Axes::with_y(vec![TrackSizing::Relative(gutter.into())]), cells, })) } diff --git a/src/library/structure/table.rs b/src/library/structure/table.rs index 9f89cd2bd..7994f196d 100644 --- a/src/library/structure/table.rs +++ b/src/library/structure/table.rs @@ -5,9 +5,9 @@ use crate::library::prelude::*; #[derive(Debug, Hash)] pub struct TableNode { /// Defines sizing for content rows and columns. - pub tracks: Spec>, + pub tracks: Axes>, /// Defines sizing of gutter rows and columns between content. - pub gutter: Spec>, + pub gutter: Axes>, /// The nodes to be arranged in the table. pub cells: Vec, } @@ -21,7 +21,7 @@ impl TableNode { #[property(resolve, fold)] pub const STROKE: Option = Some(RawStroke::default()); /// How much to pad the cells's content. - pub const PADDING: Relative = Length::pt(5.0).into(); + pub const PADDING: Rel = Abs::pt(5.0).into(); /// The spacing above the table. #[property(resolve, shorthand(around))] @@ -37,8 +37,8 @@ impl TableNode { let column_gutter = args.named("column-gutter")?; let row_gutter = args.named("row-gutter")?; Ok(Content::show(Self { - tracks: Spec::new(columns, rows), - gutter: Spec::new( + tracks: Axes::new(columns, rows), + gutter: Axes::new( column_gutter.unwrap_or_else(|| base_gutter.clone()), row_gutter.unwrap_or(base_gutter), ), diff --git a/src/library/text/deco.rs b/src/library/text/deco.rs index 1242488bf..6a4905c83 100644 --- a/src/library/text/deco.rs +++ b/src/library/text/deco.rs @@ -26,10 +26,10 @@ impl DecoNode { /// Position of the line relative to the baseline, read from the font tables /// if `auto`. #[property(resolve)] - pub const OFFSET: Smart = Smart::Auto; + pub const OFFSET: Smart = Smart::Auto; /// Amount that the line will be longer or shorter than its associated text. #[property(resolve)] - pub const EXTENT: RawLength = RawLength::zero(); + pub const EXTENT: Length = Length::zero(); /// Whether the line skips sections in which it would collide /// with the glyphs. Does not apply to strikethrough. pub const EVADE: bool = true; @@ -69,9 +69,9 @@ impl Show for DecoNode { #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Decoration { pub line: DecoLine, - pub stroke: RawStroke, - pub offset: Smart, - pub extent: Length, + pub stroke: RawStroke, + pub offset: Smart, + pub extent: Abs, pub evade: bool, } @@ -92,9 +92,9 @@ pub fn decorate( frame: &mut Frame, deco: &Decoration, text: &Text, - shift: Length, + shift: Abs, pos: Point, - width: Length, + width: Abs, ) { let font_metrics = text.font.metrics(); let metrics = match deco.line { @@ -116,9 +116,9 @@ pub fn decorate( let mut start = pos.x - deco.extent; let end = pos.x + (width + 2.0 * deco.extent); - let mut push_segment = |from: Length, to: Length| { + let mut push_segment = |from: Abs, to: Abs| { let origin = Point::new(from, pos.y + offset); - let target = Point::new(to - from, Length::zero()); + let target = Point::new(to - from, Abs::zero()); if target.x >= min_width || !evade { let shape = Geometry::Line(target).stroked(stroke); @@ -161,7 +161,7 @@ pub fn decorate( intersections.extend( path.segments() .flat_map(|seg| seg.intersect_line(line)) - .map(|is| Length::raw(line.eval(is.line_t).x)), + .map(|is| Abs::raw(line.eval(is.line_t).x)), ); } } @@ -196,12 +196,12 @@ pub fn decorate( struct BezPathBuilder { path: BezPath, units_per_em: f64, - font_size: Length, + font_size: Abs, x_offset: f64, } impl BezPathBuilder { - fn new(units_per_em: f64, font_size: Length, x_offset: f64) -> Self { + fn new(units_per_em: f64, font_size: Abs, x_offset: f64) -> Self { Self { path: BezPath::new(), units_per_em, diff --git a/src/library/text/link.rs b/src/library/text/link.rs index 78ae4d231..7c4a95edf 100644 --- a/src/library/text/link.rs +++ b/src/library/text/link.rs @@ -43,9 +43,9 @@ castable! { Value::Str(string) => Self::Url(string.into()), Value::Dict(dict) => { let page = dict.get("page")?.clone().cast()?; - let x: RawLength = dict.get("x")?.clone().cast()?; - let y: RawLength = dict.get("y")?.clone().cast()?; - Self::Internal(Location { page, pos: Point::new(x.length, y.length) }) + let x: Length = dict.get("x")?.clone().cast()?; + let y: Length = dict.get("y")?.clone().cast()?; + Self::Internal(Location { page, pos: Point::new(x.abs, y.abs) }) }, } diff --git a/src/library/text/mod.rs b/src/library/text/mod.rs index 299357de7..2d302b9a9 100644 --- a/src/library/text/mod.rs +++ b/src/library/text/mod.rs @@ -48,19 +48,19 @@ impl TextNode { /// The size of the glyphs. #[property(shorthand, fold)] - pub const SIZE: TextSize = Length::pt(11.0); + pub const SIZE: TextSize = Abs::pt(11.0); /// The glyph fill color. #[property(shorthand)] pub const FILL: Paint = Color::BLACK.into(); /// The amount of space that should be added between characters. #[property(resolve)] - pub const TRACKING: RawLength = RawLength::zero(); + pub const TRACKING: Length = Length::zero(); /// The width of spaces relative to the font's space width. #[property(resolve)] - pub const SPACING: Relative = Relative::one(); + pub const SPACING: Rel = Rel::one(); /// The offset of the baseline. #[property(resolve)] - pub const BASELINE: RawLength = RawLength::zero(); + pub const BASELINE: Length = Length::zero(); /// Whether certain glyphs can hang over into the margin. pub const OVERHANG: bool = true; /// The top end of the text bounding box. @@ -243,17 +243,17 @@ castable! { /// The size of text. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct TextSize(pub RawLength); +pub struct TextSize(pub Length); impl Fold for TextSize { - type Output = Length; + type Output = Abs; fn fold(self, outer: Self::Output) -> Self::Output { - self.0.em.at(outer) + self.0.length + self.0.em.at(outer) + self.0.abs } } -castable!(TextSize: RawLength); +castable!(TextSize: Length); /// Specifies the bottom or top edge of text. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] @@ -261,12 +261,12 @@ pub enum TextEdge { /// An edge specified using one of the well-known font metrics. Metric(VerticalFontMetric), /// An edge specified as a length. - Length(RawLength), + Length(Length), } impl TextEdge { /// Resolve the value of the text edge given a font's metrics. - pub fn resolve(self, styles: StyleChain, metrics: &FontMetrics) -> Length { + pub fn resolve(self, styles: StyleChain, metrics: &FontMetrics) -> Abs { match self { Self::Metric(metric) => metrics.vertical(metric).resolve(styles), Self::Length(length) => length.resolve(styles), @@ -310,8 +310,8 @@ castable! { HorizontalDir, Expected: "direction", @dir: Dir => match dir.axis() { - SpecAxis::Horizontal => Self(*dir), - SpecAxis::Vertical => Err("must be horizontal")?, + Axis::X => Self(*dir), + Axis::Y => Err("must be horizontal")?, }, } diff --git a/src/library/text/par.rs b/src/library/text/par.rs index 859a7c87d..477bf97fd 100644 --- a/src/library/text/par.rs +++ b/src/library/text/par.rs @@ -30,13 +30,13 @@ pub enum ParChild { impl ParNode { /// The spacing between lines. #[property(resolve)] - pub const LEADING: RawLength = Em::new(0.65).into(); + pub const LEADING: Length = Em::new(0.65).into(); /// The extra spacing between paragraphs. #[property(resolve)] - pub const SPACING: RawLength = Em::new(1.2).into(); + pub const SPACING: Length = Em::new(1.2).into(); /// The indent the first line of a consecutive paragraph should have. #[property(resolve)] - pub const INDENT: RawLength = RawLength::zero(); + pub const INDENT: Length = Length::zero(); /// Whether to allow paragraph spacing when there is paragraph indent. pub const SPACING_AND_INDENT: bool = false; @@ -119,8 +119,8 @@ castable! { HorizontalAlign, Expected: "alignment", @align: RawAlign => match align.axis() { - SpecAxis::Horizontal => Self(*align), - SpecAxis::Vertical => Err("must be horizontal")?, + Axis::X => Self(*align), + Axis::Y => Err("must be horizontal")?, }, } @@ -212,7 +212,7 @@ struct Preparation<'a> { /// The text language if it's the same for all children. lang: Option, /// The resolved leading between lines. - leading: Length, + leading: Abs, /// The paragraph's resolved alignment. align: Align, /// Whether to justify the paragraph. @@ -292,9 +292,9 @@ enum Item<'a> { /// A shaped text run with consistent style and direction. Text(ShapedText<'a>), /// Absolute spacing between other items. - Absolute(Length), + Absolute(Abs), /// Fractional spacing between other items. - Fractional(Fraction), + Fractional(Fr), /// A layouted child node. Frame(Frame), /// A repeating node that fills the remaining space. @@ -320,12 +320,12 @@ impl<'a> Item<'a> { } /// The natural layouted width of the item. - fn width(&self) -> Length { + fn width(&self) -> Abs { match self { Self::Text(shaped) => shaped.width, Self::Absolute(v) => *v, Self::Frame(frame) => frame.width(), - Self::Fractional(_) | Self::Repeat(_, _) => Length::zero(), + Self::Fractional(_) | Self::Repeat(_, _) => Abs::zero(), } } } @@ -354,7 +354,7 @@ struct Line<'a> { /// there is only one text item, this takes precedence over `first`. last: Option>, /// The width of the line. - width: Length, + width: Abs, /// Whether the line should be justified. justify: bool, /// Whether the line ends with a hyphen or dash, either naturally or through @@ -402,8 +402,8 @@ impl<'a> Line<'a> { } /// How much of the line is stretchable spaces. - fn stretch(&self) -> Length { - let mut stretch = Length::zero(); + fn stretch(&self) -> Abs { + let mut stretch = Abs::zero(); for shaped in self.items().filter_map(Item::text) { stretch += shaped.stretch(); } @@ -411,11 +411,11 @@ impl<'a> Line<'a> { } /// The sum of fractions in the line. - fn fr(&self) -> Fraction { + fn fr(&self) -> Fr { self.items() .filter_map(|item| match item { Item::Fractional(fr) => Some(*fr), - Item::Repeat(_, _) => Some(Fraction::one()), + Item::Repeat(_, _) => Some(Fr::one()), _ => None, }) .sum() @@ -533,7 +533,7 @@ fn prepare<'a>( items.push(Item::Repeat(repeat, styles)); } else { let size = Size::new(regions.first.x, regions.base.y); - let pod = Regions::one(size, regions.base, Spec::splat(false)); + let pod = Regions::one(size, regions.base, Axes::splat(false)); let mut frame = node.layout(world, &pod, styles)?.remove(0); frame.translate(Point::with_y(styles.get(TextNode::BASELINE))); frame.apply_role(Role::GenericInline); @@ -628,7 +628,7 @@ fn shared_get<'a, K: Key<'a>>( fn linebreak<'a>( p: &'a Preparation<'a>, world: Tracked, - width: Length, + width: Abs, ) -> Vec> { match p.styles.get(ParNode::LINEBREAKS) { Linebreaks::Simple => linebreak_simple(p, world, width), @@ -642,7 +642,7 @@ fn linebreak<'a>( fn linebreak_simple<'a>( p: &'a Preparation<'a>, world: Tracked, - width: Length, + width: Abs, ) -> Vec> { let mut lines = vec![]; let mut start = 0; @@ -702,7 +702,7 @@ fn linebreak_simple<'a>( fn linebreak_optimized<'a>( p: &'a Preparation<'a>, world: Tracked, - width: Length, + width: Abs, ) -> Vec> { /// The cost of a line or paragraph layout. type Cost = f64; @@ -930,7 +930,7 @@ fn line<'a>( first: None, inner: &[], last: None, - width: Length::zero(), + width: Abs::zero(), justify, dash: false, }; @@ -938,7 +938,7 @@ fn line<'a>( // Slice out the relevant items. let (expanded, mut inner) = p.slice(range.clone()); - let mut width = Length::zero(); + let mut width = Abs::zero(); // Reshape the last item if it's split in half or hyphenated. let mut last = None; @@ -1075,10 +1075,10 @@ fn commit( world: Tracked, line: &Line, regions: &Regions, - width: Length, + width: Abs, ) -> SourceResult { let mut remaining = width - line.width; - let mut offset = Length::zero(); + let mut offset = Abs::zero(); // Reorder the line from logical to visual order. let reordered = reorder(line); @@ -1112,22 +1112,22 @@ fn commit( // Determine how much to justify each space. let fr = line.fr(); - let mut justification = Length::zero(); - if remaining < Length::zero() || (line.justify && fr.is_zero()) { + let mut justification = Abs::zero(); + if remaining < Abs::zero() || (line.justify && fr.is_zero()) { let justifiables = line.justifiables(); if justifiables > 0 { justification = remaining / justifiables as f64; - remaining = Length::zero(); + remaining = Abs::zero(); } } - let mut top = Length::zero(); - let mut bottom = Length::zero(); + let mut top = Abs::zero(); + let mut bottom = Abs::zero(); // Build the frames and determine the height and baseline. let mut frames = vec![]; for item in reordered { - let mut push = |offset: &mut Length, frame: Frame| { + let mut push = |offset: &mut Abs, frame: Frame| { let width = frame.width(); top.set_max(frame.baseline()); bottom.set_max(frame.size().y - frame.baseline()); @@ -1151,9 +1151,9 @@ fn commit( } Item::Repeat(node, styles) => { let before = offset; - let fill = Fraction::one().share(fr, remaining); + let fill = Fr::one().share(fr, remaining); let size = Size::new(fill, regions.base.y); - let pod = Regions::one(size, regions.base, Spec::new(false, false)); + let pod = Regions::one(size, regions.base, Axes::new(false, false)); let frame = node.layout(world, &pod, *styles)?.remove(0); let width = frame.width(); let count = (fill / width).floor(); @@ -1162,7 +1162,7 @@ fn commit( if count == 1.0 { offset += p.align.position(remaining); } - if width > Length::zero() { + if width > Abs::zero() { for _ in 0 .. (count as usize).min(1000) { push(&mut offset, frame.clone()); offset += apart; @@ -1175,7 +1175,7 @@ fn commit( // Remaining space is distributed now. if !fr.is_zero() { - remaining = Length::zero(); + remaining = Abs::zero(); } let size = Size::new(width, top + bottom); diff --git a/src/library/text/shaping.rs b/src/library/text/shaping.rs index 487b90901..9ac3db1c1 100644 --- a/src/library/text/shaping.rs +++ b/src/library/text/shaping.rs @@ -23,9 +23,9 @@ pub struct ShapedText<'a> { /// The font variant. pub variant: FontVariant, /// The font size. - pub size: Length, + pub size: Abs, /// The width of the text's bounding box. - pub width: Length, + pub width: Abs, /// The shaped glyphs. pub glyphs: Cow<'a, [ShapedGlyph]>, } @@ -80,11 +80,11 @@ impl<'a> ShapedText<'a> { /// /// The `justification` defines how much extra advance width each /// [justifiable glyph](ShapedGlyph::is_justifiable) will get. - pub fn build(&self, world: Tracked, justification: Length) -> Frame { + pub fn build(&self, world: Tracked, justification: Abs) -> Frame { let (top, bottom) = self.measure(world); let size = Size::new(self.width, top + bottom); - let mut offset = Length::zero(); + let mut offset = Abs::zero(); let mut frame = Frame::new(size); frame.set_baseline(top); @@ -144,9 +144,9 @@ impl<'a> ShapedText<'a> { } /// Measure the top and bottom extent of this text. - fn measure(&self, world: Tracked) -> (Length, Length) { - let mut top = Length::zero(); - let mut bottom = Length::zero(); + fn measure(&self, world: Tracked) -> (Abs, Abs) { + let mut top = Abs::zero(); + let mut bottom = Abs::zero(); let top_edge = self.styles.get(TextNode::TOP_EDGE); let bottom_edge = self.styles.get(TextNode::BOTTOM_EDGE); @@ -186,7 +186,7 @@ impl<'a> ShapedText<'a> { } /// The width of the spaces in the text. - pub fn stretch(&self) -> Length { + pub fn stretch(&self) -> Abs { self.glyphs .iter() .filter(|g| g.is_justifiable()) @@ -310,7 +310,7 @@ struct ShapingContext<'a> { glyphs: Vec, used: Vec, styles: StyleChain<'a>, - size: Length, + size: Abs, variant: FontVariant, tags: Vec, fallback: bool, diff --git a/src/library/text/shift.rs b/src/library/text/shift.rs index 6a5415e8a..adad5dc0d 100644 --- a/src/library/text/shift.rs +++ b/src/library/text/shift.rs @@ -23,7 +23,7 @@ impl ShiftNode { /// font. pub const TYPOGRAPHIC: bool = true; /// The baseline shift for synthetic sub- and superscripts. - pub const BASELINE: RawLength = + pub const BASELINE: Length = Em::new(if S == SUPERSCRIPT { -0.5 } else { 0.2 }).into(); /// The font size for synthetic sub- and superscripts. pub const SIZE: TextSize = TextSize(Em::new(0.6).into()); diff --git a/src/model/content.rs b/src/model/content.rs index 428865f33..3ee59eccc 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -15,7 +15,7 @@ use super::{ }; use crate::diag::{SourceResult, StrResult}; use crate::frame::{Frame, Role}; -use crate::geom::{Length, Numeric}; +use crate::geom::{Abs, Numeric}; use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing}; use crate::library::structure::{DocNode, ListItem, ListNode, DESC, ENUM, LIST}; use crate::library::text::{ParChild, ParNode}; @@ -175,7 +175,7 @@ impl Content { } /// Add weak vertical spacing above and below the node. - pub fn spaced(self, above: Option, below: Option) -> Self { + pub fn spaced(self, above: Option, below: Option) -> Self { if above.is_none() && below.is_none() { return self; } diff --git a/src/model/eval.rs b/src/model/eval.rs index 6658e244e..c16156cda 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -11,7 +11,7 @@ use super::{ Pattern, Recipe, Scope, Scopes, StyleEntry, StyleMap, Value, Vm, }; use crate::diag::{At, SourceResult, StrResult, Trace, Tracepoint}; -use crate::geom::{Angle, Em, Fraction, Length, Ratio}; +use crate::geom::{Abs, Angle, Em, Fr, Ratio}; use crate::library; use crate::syntax::ast::TypedNode; use crate::syntax::{ast, SourceId, Span, Spanned, Unit}; @@ -465,10 +465,10 @@ impl Eval for ast::Lit { ast::LitKind::Int(v) => Value::Int(v), ast::LitKind::Float(v) => Value::Float(v), ast::LitKind::Numeric(v, unit) => match unit { - Unit::Length(unit) => Length::with_unit(v, unit).into(), + Unit::Length(unit) => Abs::with_unit(v, unit).into(), Unit::Angle(unit) => Angle::with_unit(v, unit).into(), Unit::Em => Em::new(v).into(), - Unit::Fr => Fraction::new(v).into(), + Unit::Fr => Fr::new(v).into(), Unit::Percent => Ratio::new(v / 100.0).into(), }, ast::LitKind::Str(v) => Value::Str(v.into()), diff --git a/src/model/layout.rs b/src/model/layout.rs index 5248157b0..ea9d076c4 100644 --- a/src/model/layout.rs +++ b/src/model/layout.rs @@ -8,10 +8,10 @@ use std::sync::Arc; use comemo::{Prehashed, Tracked}; use super::{Barrier, NodeId, Resolve, StyleChain, StyleEntry}; -use super::{Builder, Content, RawLength, Scratch}; +use super::{Builder, Content, Scratch}; use crate::diag::SourceResult; use crate::frame::{Element, Frame}; -use crate::geom::{Align, Geometry, Length, Paint, Point, Relative, Size, Spec, Stroke}; +use crate::geom::{Abs, Align, Axes, Geometry, Length, Paint, Point, Rel, Size, Stroke}; use crate::World; /// Layout content into a collection of pages. @@ -56,18 +56,18 @@ pub struct Regions { /// The base size for relative sizing. pub base: Size, /// The height of followup regions. The width is the same for all regions. - pub backlog: Vec, + pub backlog: Vec, /// The height of the final region that is repeated once the backlog is /// drained. The width is the same for all regions. - pub last: Option, + pub last: Option, /// Whether nodes should expand to fill the regions instead of shrinking to /// fit the content. - pub expand: Spec, + pub expand: Axes, } impl Regions { /// Create a new region sequence with exactly one region. - pub fn one(size: Size, base: Size, expand: Spec) -> Self { + pub fn one(size: Size, base: Size, expand: Axes) -> Self { Self { first: size, base, @@ -78,7 +78,7 @@ impl Regions { } /// Create a new sequence of same-size regions that repeats indefinitely. - pub fn repeat(size: Size, base: Size, expand: Spec) -> Self { + pub fn repeat(size: Size, base: Size, expand: Axes) -> Self { Self { first: size, base, @@ -108,7 +108,7 @@ impl Regions { /// Whether the first region is full and a region break is called for. pub fn is_full(&self) -> bool { - Length::zero().fits(self.first.y) && !self.in_last() + Abs::zero().fits(self.first.y) && !self.in_last() } /// Whether the first region is the last usable region. @@ -173,7 +173,7 @@ impl LayoutNode { } /// Force a size for this node. - pub fn sized(self, sizing: Spec>>) -> Self { + pub fn sized(self, sizing: Axes>>) -> Self { if sizing.any(Option::is_some) { SizedNode { sizing, child: self }.pack() } else { @@ -279,7 +279,7 @@ impl Layout for EmptyNode { #[derive(Debug, Hash)] struct SizedNode { /// How to size the node horizontally and vertically. - sizing: Spec>>, + sizing: Axes>>, /// The node to be sized. child: LayoutNode, } @@ -303,7 +303,7 @@ impl Layout for SizedNode { // Select the appropriate base and expansion for the child depending // on whether it is automatically or relatively sized. - let is_auto = self.sizing.map_is_none(); + let is_auto = self.sizing.as_ref().map(Option::is_none); let base = is_auto.select(regions.base, size); let expand = regions.expand | !is_auto; diff --git a/src/model/ops.rs b/src/model/ops.rs index c521f7045..c7d6ac783 100644 --- a/src/model/ops.rs +++ b/src/model/ops.rs @@ -2,9 +2,9 @@ use std::cmp::Ordering; -use super::{RawAlign, RawLength, RawStroke, Regex, Smart, Value}; +use super::{RawAlign, RawStroke, Regex, Smart, Value}; use crate::diag::StrResult; -use crate::geom::{Numeric, Relative, Spec, SpecAxis}; +use crate::geom::{Axes, Axis, Length, Numeric, Rel}; use Value::*; /// Bail with a type mismatch error. @@ -106,8 +106,8 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult { { if a.axis() != b.axis() { Value::dynamic(match a.axis() { - SpecAxis::Horizontal => Spec { x: a, y: b }, - SpecAxis::Vertical => Spec { x: b, y: a }, + Axis::X => Axes { x: a, y: b }, + Axis::Y => Axes { x: b, y: a }, }) } else { return Err(format!("cannot add two {:?} alignments", a.axis())); @@ -203,8 +203,8 @@ pub fn div(lhs: Value, rhs: Value) -> StrResult { (Length(a), Int(b)) => Length(a / b as f64), (Length(a), Float(b)) => Length(a / b), - (Length(a), Length(b)) => Float(div_length(a, b)?), - (Length(a), Relative(b)) if b.rel.is_zero() => Float(div_length(a, b.abs)?), + (Length(a), Length(b)) => Float(try_div_length(a, b)?), + (Length(a), Relative(b)) if b.rel.is_zero() => Float(try_div_length(a, b.abs)?), (Angle(a), Int(b)) => Angle(a / b as f64), (Angle(a), Float(b)) => Angle(a / b), @@ -217,9 +217,9 @@ pub fn div(lhs: Value, rhs: Value) -> StrResult { (Relative(a), Int(b)) => Relative(a / b as f64), (Relative(a), Float(b)) => Relative(a / b), - (Relative(a), Length(b)) if a.rel.is_zero() => Float(div_length(a.abs, b)?), + (Relative(a), Length(b)) if a.rel.is_zero() => Float(try_div_length(a.abs, b)?), (Relative(a), Ratio(b)) if a.abs.is_zero() => Float(a.rel / b), - (Relative(a), Relative(b)) => Float(div_relative(a, b)?), + (Relative(a), Relative(b)) => Float(try_div_relative(a, b)?), (Fraction(a), Int(b)) => Fraction(a / b as f64), (Fraction(a), Float(b)) => Fraction(a / b), @@ -230,25 +230,14 @@ pub fn div(lhs: Value, rhs: Value) -> StrResult { } /// Try to divide two lengths. -fn div_length(a: RawLength, b: RawLength) -> StrResult { - if a.length.is_zero() && b.length.is_zero() { - Ok(a.em / b.em) - } else if a.em.is_zero() && b.em.is_zero() { - Ok(a.length / b.length) - } else { - return Err("cannot divide these two lengths".into()); - } +fn try_div_length(a: Length, b: Length) -> StrResult { + a.try_div(b).ok_or_else(|| "cannot divide these two lengths".into()) } /// Try to divide two relative lengths. -fn div_relative(a: Relative, b: Relative) -> StrResult { - if a.rel.is_zero() && b.rel.is_zero() { - div_length(a.abs, b.abs) - } else if a.abs.is_zero() && b.abs.is_zero() { - Ok(a.rel / b.rel) - } else { - return Err("cannot divide these two relative lengths".into()); - } +fn try_div_relative(a: Rel, b: Rel) -> StrResult { + a.try_div(b) + .ok_or_else(|| "cannot divide these two relative lengths".into()) } /// Compute the logical "not" of a value. diff --git a/src/model/property.rs b/src/model/property.rs index 4b9342ab2..2e2a76a56 100644 --- a/src/model/property.rs +++ b/src/model/property.rs @@ -5,11 +5,11 @@ use std::sync::Arc; use comemo::Prehashed; -use super::{Interruption, NodeId, RawLength, Smart, StyleChain}; -use crate::geom::{Corners, Length, Numeric, Relative, Sides, Spec}; +use super::{Interruption, NodeId, Smart, StyleChain}; +use crate::geom::{Abs, Axes, Corners, Em, Length, Numeric, Rel, Sides}; use crate::library::layout::PageNode; use crate::library::structure::{DescNode, EnumNode, ListNode}; -use crate::library::text::ParNode; +use crate::library::text::{ParNode, TextNode}; use crate::util::ReadableTypeId; /// A style property originating from a set rule or constructor. @@ -163,6 +163,26 @@ pub trait Resolve { fn resolve(self, styles: StyleChain) -> Self::Output; } +impl Resolve for Em { + type Output = Abs; + + fn resolve(self, styles: StyleChain) -> Self::Output { + if self.is_zero() { + Abs::zero() + } else { + self.at(styles.get(TextNode::SIZE)) + } + } +} + +impl Resolve for Length { + type Output = Abs; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.abs + self.em.resolve(styles) + } +} + impl Resolve for Option { type Output = Option; @@ -179,8 +199,8 @@ impl Resolve for Smart { } } -impl Resolve for Spec { - type Output = Spec; +impl Resolve for Axes { + type Output = Axes; fn resolve(self, styles: StyleChain) -> Self::Output { self.map(|v| v.resolve(styles)) @@ -203,12 +223,12 @@ impl Resolve for Corners { } } -impl Resolve for Relative +impl Resolve for Rel where T: Resolve + Numeric, ::Output: Numeric, { - type Output = Relative<::Output>; + type Output = Rel<::Output>; fn resolve(self, styles: StyleChain) -> Self::Output { self.map(|abs| abs.resolve(styles)) @@ -259,16 +279,16 @@ where } } -impl Fold for Sides>> { - type Output = Sides>; +impl Fold for Sides>> { + type Output = Sides>; fn fold(self, outer: Self::Output) -> Self::Output { self.zip(outer, |inner, outer| inner.unwrap_or(outer)) } } -impl Fold for Sides>>> { - type Output = Sides>>; +impl Fold for Sides>>> { + type Output = Sides>>; fn fold(self, outer: Self::Output) -> Self::Output { self.zip(outer, |inner, outer| inner.unwrap_or(outer)) @@ -286,8 +306,8 @@ where } } -impl Fold for Corners>> { - type Output = Corners>; +impl Fold for Corners>> { + type Output = Corners>; fn fold(self, outer: Self::Output) -> Self::Output { self.zip(outer, |inner, outer| inner.unwrap_or(outer)) diff --git a/src/model/raw.rs b/src/model/raw.rs index b40a88ec4..b9242b51e 100644 --- a/src/model/raw.rs +++ b/src/model/raw.rs @@ -1,11 +1,7 @@ -use std::cmp::Ordering; use std::fmt::{self, Debug, Formatter}; -use std::ops::{Add, Div, Mul, Neg}; use super::{Fold, Resolve, Smart, StyleChain, Value}; -use crate::geom::{ - Align, Em, Get, Length, Numeric, Paint, Relative, Spec, SpecAxis, Stroke, -}; +use crate::geom::{Abs, Align, Axes, Axis, Get, Length, Paint, Stroke}; use crate::library::text::TextNode; /// The unresolved alignment representation. @@ -34,9 +30,9 @@ impl Resolve for RawAlign { impl RawAlign { /// The axis this alignment belongs to. - pub const fn axis(self) -> SpecAxis { + pub const fn axis(self) -> Axis { match self { - Self::Start | Self::End => SpecAxis::Horizontal, + Self::Start | Self::End => Axis::X, Self::Specific(align) => align.axis(), } } @@ -51,8 +47,8 @@ impl From for RawAlign { impl Debug for RawAlign { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - Self::Start => f.pad("left"), - Self::End => f.pad("center"), + Self::Start => f.pad("start"), + Self::End => f.pad("end"), Self::Specific(align) => align.fmt(f), } } @@ -63,18 +59,18 @@ dynamic! { } dynamic! { - Spec: "2d alignment", + Axes: "2d alignment", } castable! { - Spec>, + Axes>, Expected: "1d or 2d alignment", @align: RawAlign => { - let mut aligns = Spec::default(); + let mut aligns = Axes::default(); aligns.set(align.axis(), Some(*align)); aligns }, - @aligns: Spec => aligns.map(Some), + @aligns: Axes => aligns.map(Some), } /// The unresolved stroke representation. @@ -83,14 +79,14 @@ castable! { /// just a paint (`red`), just a thickness (`0.1em`) or both (`2pt + red`) where /// this is expected. #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct RawStroke { +pub struct RawStroke { /// The stroke's paint. pub paint: Smart, /// The stroke's thickness. pub thickness: Smart, } -impl RawStroke { +impl RawStroke { /// Unpack the stroke, filling missing fields from the `default`. pub fn unwrap_or(self, default: Stroke) -> Stroke { Stroke { @@ -106,7 +102,7 @@ impl RawStroke { } impl Resolve for RawStroke { - type Output = RawStroke; + type Output = RawStroke; fn resolve(self, styles: StyleChain) -> Self::Output { RawStroke { @@ -116,7 +112,7 @@ impl Resolve for RawStroke { } } -impl Fold for RawStroke { +impl Fold for RawStroke { type Output = Self; fn fold(self, outer: Self::Output) -> Self::Output { @@ -151,143 +147,3 @@ dynamic! { thickness: Smart::Auto, }, } - -/// The unresolved length representation. -/// -/// Currently supports absolute and em units, but support could quite easily be -/// extended to other units that can be resolved through a style chain. -/// Probably, it would be a good idea to then move to an enum representation -/// that has a small footprint and allocates for the rare case that units are -/// mixed. -#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct RawLength { - /// The absolute part. - pub length: Length, - /// The font-relative part. - pub em: Em, -} - -impl RawLength { - /// The zero length. - pub const fn zero() -> Self { - Self { length: Length::zero(), em: Em::zero() } - } -} - -impl Debug for RawLength { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match (self.length.is_zero(), self.em.is_zero()) { - (false, false) => write!(f, "{:?} + {:?}", self.length, self.em), - (true, false) => self.em.fmt(f), - (_, true) => self.length.fmt(f), - } - } -} - -impl Resolve for Em { - type Output = Length; - - fn resolve(self, styles: StyleChain) -> Self::Output { - if self.is_zero() { - Length::zero() - } else { - self.at(styles.get(TextNode::SIZE)) - } - } -} - -impl Resolve for RawLength { - type Output = Length; - - fn resolve(self, styles: StyleChain) -> Self::Output { - self.length + self.em.resolve(styles) - } -} - -impl Numeric for RawLength { - fn zero() -> Self { - Self::zero() - } - - fn is_finite(self) -> bool { - self.length.is_finite() && self.em.is_finite() - } -} - -impl PartialOrd for RawLength { - fn partial_cmp(&self, other: &Self) -> Option { - if self.em.is_zero() && other.em.is_zero() { - self.length.partial_cmp(&other.length) - } else if self.length.is_zero() && other.length.is_zero() { - self.em.partial_cmp(&other.em) - } else { - None - } - } -} - -impl From for RawLength { - fn from(length: Length) -> Self { - Self { length, em: Em::zero() } - } -} - -impl From for RawLength { - fn from(em: Em) -> Self { - Self { length: Length::zero(), em } - } -} - -impl From for Relative { - fn from(length: Length) -> Self { - Relative::from(RawLength::from(length)) - } -} - -impl Neg for RawLength { - type Output = Self; - - fn neg(self) -> Self::Output { - Self { length: -self.length, em: -self.em } - } -} - -impl Add for RawLength { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self { - length: self.length + rhs.length, - em: self.em + rhs.em, - } - } -} - -sub_impl!(RawLength - RawLength -> RawLength); - -impl Mul for RawLength { - type Output = Self; - - fn mul(self, rhs: f64) -> Self::Output { - Self { - length: self.length * rhs, - em: self.em * rhs, - } - } -} - -impl Div for RawLength { - type Output = Self; - - fn div(self, rhs: f64) -> Self::Output { - Self { - length: self.length / rhs, - em: self.em / rhs, - } - } -} - -assign_impl!(RawLength += RawLength); -assign_impl!(RawLength -= RawLength); -assign_impl!(RawLength *= f64); -assign_impl!(RawLength /= f64); diff --git a/src/model/value.rs b/src/model/value.rs index e0d5edf3d..7b594adf1 100644 --- a/src/model/value.rs +++ b/src/model/value.rs @@ -7,9 +7,9 @@ use std::sync::Arc; use comemo::Tracked; use siphasher::sip128::{Hasher128, SipHasher}; -use super::{ops, Args, Array, Cast, Content, Dict, Func, Layout, RawLength, Str}; +use super::{ops, Args, Array, Cast, Content, Dict, Func, Layout, Str}; use crate::diag::StrResult; -use crate::geom::{Angle, Color, Em, Fraction, Length, Ratio, Relative, RgbaColor}; +use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor}; use crate::util::EcoString; use crate::World; @@ -26,16 +26,16 @@ pub enum Value { Int(i64), /// A floating-point number: `1.2`, `10e-4`. Float(f64), - /// A length: `12pt`, `3cm`, `1.5em`. - Length(RawLength), + /// A length: `12pt`, `3cm`, `1.5em`, `1em - 2pt`. + Length(Length), /// An angle: `1.5rad`, `90deg`. Angle(Angle), /// A ratio: `50%`. Ratio(Ratio), /// A relative length, combination of a ratio and a length: `20% + 5cm`. - Relative(Relative), + Relative(Rel), /// A fraction: `1fr`. - Fraction(Fraction), + Fraction(Fr), /// A color value: `#f79143ff`. Color(Color), /// A string: `"string"`. @@ -87,11 +87,11 @@ impl Value { Self::Bool(_) => bool::TYPE_NAME, Self::Int(_) => i64::TYPE_NAME, Self::Float(_) => f64::TYPE_NAME, - Self::Length(_) => RawLength::TYPE_NAME, + Self::Length(_) => Length::TYPE_NAME, Self::Angle(_) => Angle::TYPE_NAME, Self::Ratio(_) => Ratio::TYPE_NAME, - Self::Relative(_) => Relative::::TYPE_NAME, - Self::Fraction(_) => Fraction::TYPE_NAME, + Self::Relative(_) => Rel::::TYPE_NAME, + Self::Fraction(_) => Fr::TYPE_NAME, Self::Color(_) => Color::TYPE_NAME, Self::Str(_) => Str::TYPE_NAME, Self::Content(_) => Content::TYPE_NAME, @@ -210,8 +210,8 @@ impl From for Value { } } -impl From for Value { - fn from(v: Length) -> Self { +impl From for Value { + fn from(v: Abs) -> Self { Self::Length(v.into()) } } @@ -385,15 +385,15 @@ macro_rules! primitive { primitive! { bool: "boolean", Bool } primitive! { i64: "integer", Int } primitive! { f64: "float", Float, Int(v) => v as f64 } -primitive! { RawLength: "length", Length } +primitive! { Length: "length", Length } primitive! { Angle: "angle", Angle } primitive! { Ratio: "ratio", Ratio } -primitive! { Relative: "relative length", +primitive! { Rel: "relative length", Relative, Length(v) => v.into(), Ratio(v) => v.into() } -primitive! { Fraction: "fraction", Fraction } +primitive! { Fr: "fraction", Fraction } primitive! { Color: "color", Color } primitive! { Str: "string", Str } primitive! { Content: "content", @@ -422,14 +422,14 @@ mod tests { test(false, "false"); test(12i64, "12"); test(3.14, "3.14"); - test(Length::pt(5.5), "5.5pt"); + test(Abs::pt(5.5), "5.5pt"); test(Angle::deg(90.0), "90deg"); test(Ratio::one() / 2.0, "50%"); test( - Ratio::new(0.3) + RawLength::from(Length::cm(2.0)), + Ratio::new(0.3) + Length::from(Abs::cm(2.0)), "30% + 56.69pt", ); - test(Fraction::one() * 7.55, "7.55fr"); + test(Fr::one() * 7.55, "7.55fr"); test( Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "rgb(\"#010101\")", diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs index 3273e72d8..1568693ba 100644 --- a/src/syntax/kind.rs +++ b/src/syntax/kind.rs @@ -1,7 +1,7 @@ use std::hash::{Hash, Hasher}; use std::sync::Arc; -use crate::geom::{AngleUnit, LengthUnit}; +use crate::geom::{AbsUnit, AngleUnit}; use crate::util::EcoString; /// All syntactical building blocks that can be part of a Typst document. @@ -277,7 +277,7 @@ pub struct RawKind { #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Unit { /// An absolute length unit. - Length(LengthUnit), + Length(AbsUnit), /// An angular unit. Angle(AngleUnit), /// Font-relative: `1em` is the same as the font size. diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index 8e1b69444..59e6cd3f8 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -5,7 +5,7 @@ use unscanny::Scanner; use super::resolve::{resolve_hex, resolve_raw, resolve_string}; use super::{ErrorPos, NodeKind, RawKind, Unit}; -use crate::geom::{AngleUnit, LengthUnit}; +use crate::geom::{AbsUnit, AngleUnit}; use crate::util::EcoString; /// An iterator over the tokens of a string of source code. @@ -565,10 +565,10 @@ impl<'s> Tokens<'s> { match suffix { "" => NodeKind::Float(v), - "pt" => NodeKind::Numeric(v, Unit::Length(LengthUnit::Pt)), - "mm" => NodeKind::Numeric(v, Unit::Length(LengthUnit::Mm)), - "cm" => NodeKind::Numeric(v, Unit::Length(LengthUnit::Cm)), - "in" => NodeKind::Numeric(v, Unit::Length(LengthUnit::In)), + "pt" => NodeKind::Numeric(v, Unit::Length(AbsUnit::Pt)), + "mm" => NodeKind::Numeric(v, Unit::Length(AbsUnit::Mm)), + "cm" => NodeKind::Numeric(v, Unit::Length(AbsUnit::Cm)), + "in" => NodeKind::Numeric(v, Unit::Length(AbsUnit::In)), "deg" => NodeKind::Numeric(v, Unit::Angle(AngleUnit::Deg)), "rad" => NodeKind::Numeric(v, Unit::Angle(AngleUnit::Rad)), "em" => NodeKind::Numeric(v, Unit::Em), @@ -1089,10 +1089,10 @@ mod tests { let nums = ints.iter().map(|&(k, v)| (k, v as f64)).chain(floats); let suffixes: &[(&str, fn(f64) -> NodeKind)] = &[ - ("mm", |x| Numeric(x, Unit::Length(LengthUnit::Mm))), - ("pt", |x| Numeric(x, Unit::Length(LengthUnit::Pt))), - ("cm", |x| Numeric(x, Unit::Length(LengthUnit::Cm))), - ("in", |x| Numeric(x, Unit::Length(LengthUnit::In))), + ("mm", |x| Numeric(x, Unit::Length(AbsUnit::Mm))), + ("pt", |x| Numeric(x, Unit::Length(AbsUnit::Pt))), + ("cm", |x| Numeric(x, Unit::Length(AbsUnit::Cm))), + ("in", |x| Numeric(x, Unit::Length(AbsUnit::In))), ("rad", |x| Numeric(x, Unit::Angle(AngleUnit::Rad))), ("deg", |x| Numeric(x, Unit::Angle(AngleUnit::Deg))), ("em", |x| Numeric(x, Unit::Em)), diff --git a/tests/typeset.rs b/tests/typeset.rs index 26a0c8b01..b5c14cb3e 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -17,7 +17,7 @@ use walkdir::WalkDir; use typst::diag::{FileError, FileResult}; use typst::font::{Font, FontBook}; use typst::frame::{Element, Frame}; -use typst::geom::{Length, RgbaColor, Sides}; +use typst::geom::{Abs, RgbaColor, Sides}; use typst::library::layout::PageNode; use typst::library::text::{TextNode, TextSize}; use typst::model::{Smart, StyleMap, Value}; @@ -150,13 +150,13 @@ fn config() -> Config { // exactly 100pt wide. Page height is unbounded and font size is 10pt so // that it multiplies to nice round numbers. let mut styles = StyleMap::new(); - styles.set(PageNode::WIDTH, Smart::Custom(Length::pt(120.0).into())); + styles.set(PageNode::WIDTH, Smart::Custom(Abs::pt(120.0).into())); styles.set(PageNode::HEIGHT, Smart::Auto); styles.set( PageNode::MARGINS, - Sides::splat(Some(Smart::Custom(Length::pt(10.0).into()))), + Sides::splat(Some(Smart::Custom(Abs::pt(10.0).into()))), ); - styles.set(TextNode::SIZE, TextSize(Length::pt(10.0).into())); + styles.set(TextNode::SIZE, TextSize(Abs::pt(10.0).into())); // Hook up helpers into the global scope. let mut std = typst::library::scope(); @@ -653,7 +653,7 @@ fn render(frames: &[Frame]) -> sk::Pixmap { let pixmaps: Vec<_> = frames .iter() .map(|frame| { - let limit = Length::cm(100.0); + let limit = Abs::cm(100.0); if frame.width() > limit || frame.height() > limit { panic!("overlarge frame: {:?}", frame.size()); }