From 4bb6240b401605ef6d905273db07545e14f9a21f Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 7 Apr 2022 18:04:29 +0200 Subject: [PATCH] Make `Relative` generic --- src/eval/layout.rs | 12 ++- src/eval/ops.rs | 20 ++-- src/eval/value.rs | 4 +- src/export/pdf.rs | 4 +- src/font.rs | 2 +- src/frame.rs | 4 +- src/geom/angle.rs | 45 +++++--- src/geom/em.rs | 21 ++-- src/geom/fraction.rs | 15 ++- src/geom/length.rs | 61 +++++------ src/geom/mod.rs | 24 +++++ src/geom/paint.rs | 36 +++---- src/geom/point.rs | 23 ++-- src/geom/ratio.rs | 16 ++- src/geom/relative.rs | 174 ++++++++++++++---------------- src/geom/scalar.rs | 10 ++ src/geom/sides.rs | 2 +- src/geom/spec.rs | 27 ++--- src/geom/transform.rs | 10 +- src/library/graphics/line.rs | 17 +-- src/library/graphics/shape.rs | 2 +- src/library/graphics/transform.rs | 8 +- src/library/layout/columns.rs | 4 +- src/library/layout/grid.rs | 4 +- src/library/layout/pad.rs | 6 +- src/library/layout/page.rs | 14 +-- src/library/layout/spacing.rs | 2 +- src/library/mod.rs | 13 +-- src/library/structure/list.rs | 6 +- src/library/structure/table.rs | 2 +- src/library/text/deco.rs | 12 +-- src/library/text/mod.rs | 2 +- src/library/text/par.rs | 6 +- src/library/utility/color.rs | 2 +- tests/typ/utility/color.typ | 2 +- 35 files changed, 318 insertions(+), 294 deletions(-) diff --git a/src/eval/layout.rs b/src/eval/layout.rs index f541694c8..9bf441947 100644 --- a/src/eval/layout.rs +++ b/src/eval/layout.rs @@ -8,7 +8,9 @@ use std::sync::Arc; use super::{Barrier, StyleChain}; use crate::diag::TypResult; use crate::frame::{Element, Frame, Geometry, Shape, Stroke}; -use crate::geom::{Align, Length, Paint, Point, Relative, Sides, Size, Spec, Transform}; +use crate::geom::{ + Align, Length, Numeric, Paint, Point, Relative, Sides, Size, Spec, Transform, +}; use crate::library::graphics::MoveNode; use crate::library::layout::{AlignNode, PadNode}; use crate::util::Prehashed; @@ -161,7 +163,7 @@ impl LayoutNode { } /// Force a size for this node. - pub fn sized(self, sizing: Spec>) -> Self { + pub fn sized(self, sizing: Spec>>) -> Self { if sizing.any(Option::is_some) { SizedNode { sizing, child: self }.pack() } else { @@ -189,7 +191,7 @@ impl LayoutNode { } /// Pad this node at the sides. - pub fn padded(self, padding: Sides) -> Self { + pub fn padded(self, padding: Sides>) -> Self { if !padding.left.is_zero() || !padding.top.is_zero() || !padding.right.is_zero() @@ -205,7 +207,7 @@ impl LayoutNode { pub fn moved(self, offset: Point) -> Self { if !offset.is_zero() { MoveNode { - transform: Transform::translation(offset.x, offset.y), + transform: Transform::translate(offset.x, offset.y), child: self, } .pack() @@ -292,7 +294,7 @@ impl Layout for EmptyNode { #[derive(Debug, Hash)] struct SizedNode { /// How to size the node horizontally and vertically. - sizing: Spec>, + sizing: Spec>>, /// The node to be sized. child: LayoutNode, } diff --git a/src/eval/ops.rs b/src/eval/ops.rs index 024de4335..ff21d93f9 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -2,7 +2,7 @@ use std::cmp::Ordering; use super::{Dynamic, StrExt, Value}; use crate::diag::StrResult; -use crate::geom::{Align, Spec, SpecAxis}; +use crate::geom::{Align, Numeric, Spec, SpecAxis}; use Value::*; /// Bail with a type mismatch error. @@ -66,12 +66,12 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult { (Angle(a), Angle(b)) => Angle(a + b), (Length(a), Length(b)) => Length(a + b), - (Length(a), Ratio(b)) => Relative(a + b), - (Length(a), Relative(b)) => Relative(a + b), + (Length(a), Ratio(b)) => Relative(b + a), + (Length(a), Relative(b)) => Relative(b + a), (Ratio(a), Length(b)) => Relative(a + b), (Ratio(a), Ratio(b)) => Ratio(a + b), - (Ratio(a), Relative(b)) => Relative(a + b), + (Ratio(a), Relative(b)) => Relative(b + a), (Relative(a), Length(b)) => Relative(a + b), (Relative(a), Ratio(b)) => Relative(a + b), @@ -123,15 +123,15 @@ pub fn sub(lhs: Value, rhs: Value) -> StrResult { (Angle(a), Angle(b)) => Angle(a - b), (Length(a), Length(b)) => Length(a - b), - (Length(a), Ratio(b)) => Relative(a - b), - (Length(a), Relative(b)) => Relative(a - b), + (Length(a), Ratio(b)) => Relative(-b + a), + (Length(a), Relative(b)) => Relative(-b + a), - (Ratio(a), Length(b)) => Relative(a - b), + (Ratio(a), Length(b)) => Relative(a + -b), (Ratio(a), Ratio(b)) => Ratio(a - b), - (Ratio(a), Relative(b)) => Relative(a - b), + (Ratio(a), Relative(b)) => Relative(-b + a), - (Relative(a), Length(b)) => Relative(a - b), - (Relative(a), Ratio(b)) => Relative(a - b), + (Relative(a), Length(b)) => Relative(a + -b), + (Relative(a), Ratio(b)) => Relative(a + -b), (Relative(a), Relative(b)) => Relative(a - b), (Fraction(a), Fraction(b)) => Fraction(a - b), diff --git a/src/eval/value.rs b/src/eval/value.rs index 774ae0b62..44df89e20 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -31,7 +31,7 @@ pub enum Value { /// A ratio: `50%`. Ratio(Ratio), /// A relative length, combination of a ratio and a length: `20% + 5cm`. - Relative(Relative), + Relative(Relative), /// A fraction: `1fr`. Fraction(Fraction), /// A color value: `#f79143ff`. @@ -549,7 +549,7 @@ primitive! { f64: "float", Float, Int(v) => v as f64 } primitive! { Length: "length", Length } primitive! { Angle: "angle", Angle } primitive! { Ratio: "ratio", Ratio } -primitive! { Relative: "relative length", Relative, Length(v) => v.into(), Ratio(v) => v.into() } +primitive! { Relative: "relative length", Relative, Length(v) => v.into(), Ratio(v) => v.into() } primitive! { Fraction: "fraction", Fraction } primitive! { Color: "color", Color } primitive! { EcoString: "string", Str } diff --git a/src/export/pdf.rs b/src/export/pdf.rs index 2550519be..95d20c518 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -17,7 +17,7 @@ use ttf_parser::{name_id, GlyphId, Tag}; use super::subset::subset; use crate::font::{find_name, FaceId, FontStore}; use crate::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text}; -use crate::geom::{self, Color, Em, Length, Paint, Point, Size, Transform}; +use crate::geom::{self, Color, Em, Length, Numeric, Paint, Point, Size, Transform}; use crate::image::{Image, ImageId, ImageStore, RasterImage}; use crate::Context; @@ -423,7 +423,7 @@ impl<'a> PageExporter<'a> { } fn write_group(&mut self, pos: Point, group: &Group) { - let translation = Transform::translation(pos.x, pos.y); + let translation = Transform::translate(pos.x, pos.y); self.save_state(); self.transform(translation.pre_concat(group.transform)); diff --git a/src/font.rs b/src/font.rs index 46d6b8d79..e1d0c4e64 100644 --- a/src/font.rs +++ b/src/font.rs @@ -415,7 +415,7 @@ pub enum VerticalFontMetric { Descender, /// An font-size dependent distance from the baseline (positive goes up, negative /// down). - Relative(Relative), + Relative(Relative), } /// Properties of a single font face. diff --git a/src/frame.rs b/src/frame.rs index 7cc564ccf..3c747f0d2 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -4,7 +4,9 @@ use std::fmt::{self, Debug, Formatter}; use std::sync::Arc; use crate::font::FaceId; -use crate::geom::{Align, Em, Length, Paint, Path, Point, Size, Spec, Transform}; +use crate::geom::{ + Align, Em, Length, Numeric, Paint, Path, Point, Size, Spec, Transform, +}; use crate::image::ImageId; /// A finished layout with elements at fixed positions. diff --git a/src/geom/angle.rs b/src/geom/angle.rs index b64ec77e0..a0900ce5b 100644 --- a/src/geom/angle.rs +++ b/src/geom/angle.rs @@ -10,6 +10,16 @@ impl Angle { Self(Scalar(0.0)) } + /// Create an angle from a number of raw units. + pub const fn raw(raw: f64) -> Self { + Self(Scalar(raw)) + } + + /// Create an angle from a value in a unit. + pub fn with_unit(val: f64, unit: AngularUnit) -> Self { + Self(Scalar(val * unit.raw_scale())) + } + /// Create an angle from a number of radians. pub fn rad(rad: f64) -> Self { Self::with_unit(rad, AngularUnit::Rad) @@ -20,9 +30,14 @@ impl Angle { Self::with_unit(deg, AngularUnit::Deg) } - /// Create an angle from a number of raw units. - pub const fn raw(raw: f64) -> Self { - Self(Scalar(raw)) + /// Get the value of this angle in raw units. + pub const fn to_raw(self) -> f64 { + (self.0).0 + } + + /// Get the value of this length in unit. + pub fn to_unit(self, unit: AngularUnit) -> f64 { + self.to_raw() / unit.raw_scale() } /// Convert this to a number of radians. @@ -35,9 +50,9 @@ impl Angle { self.to_unit(AngularUnit::Deg) } - /// Get the value of this angle in raw units. - pub const fn to_raw(self) -> f64 { - (self.0).0 + /// The absolute value of the this angle. + pub fn abs(self) -> Self { + Self::raw(self.to_raw().abs()) } /// Get the sine of this angle in radians. @@ -49,20 +64,15 @@ impl Angle { pub fn cos(self) -> f64 { self.to_rad().cos() } +} - /// Create an angle from a value in a unit. - pub fn with_unit(val: f64, unit: AngularUnit) -> Self { - Self(Scalar(val * unit.raw_scale())) +impl Numeric for Angle { + fn zero() -> Self { + Self::zero() } - /// Get the value of this length in unit. - pub fn to_unit(self, unit: AngularUnit) -> f64 { - self.to_raw() / unit.raw_scale() - } - - /// The absolute value of the this angle. - pub fn abs(self) -> Self { - Self::raw(self.to_raw().abs()) + fn is_finite(self) -> bool { + self.0.is_finite() } } @@ -132,6 +142,7 @@ impl Sum for Angle { Self(iter.map(|s| s.0).sum()) } } + /// Different units of angular measurement. #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub enum AngularUnit { diff --git a/src/geom/em.rs b/src/geom/em.rs index d1e90e04a..3c772d961 100644 --- a/src/geom/em.rs +++ b/src/geom/em.rs @@ -32,19 +32,24 @@ impl Em { Self(Scalar(length / font_size)) } - /// Convert to a length at the given font size. - pub fn resolve(self, font_size: Length) -> Length { - self.get() * font_size - } - /// The number of em units. pub const fn get(self) -> f64 { (self.0).0 } - /// Whether the length is zero. - pub fn is_zero(self) -> bool { - self.0 == 0.0 + /// Convert to a length at the given font size. + pub fn resolve(self, font_size: Length) -> Length { + self.get() * font_size + } +} + +impl Numeric for Em { + fn zero() -> Self { + Self::zero() + } + + fn is_finite(self) -> bool { + self.0.is_finite() } } diff --git a/src/geom/fraction.rs b/src/geom/fraction.rs index 7da857795..2f33a1342 100644 --- a/src/geom/fraction.rs +++ b/src/geom/fraction.rs @@ -25,11 +25,6 @@ impl Fraction { (self.0).0 } - /// Whether the fraction is zero. - pub fn is_zero(self) -> bool { - self.0 == 0.0 - } - /// The absolute value of the this fraction. pub fn abs(self) -> Self { Self::new(self.get().abs()) @@ -46,6 +41,16 @@ impl Fraction { } } +impl Numeric for Fraction { + fn zero() -> Self { + Self::zero() + } + + fn is_finite(self) -> bool { + self.0.is_finite() + } +} + impl Debug for Fraction { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}fr", round_2(self.get())) diff --git a/src/geom/length.rs b/src/geom/length.rs index c5fbfd76d..838d33c00 100644 --- a/src/geom/length.rs +++ b/src/geom/length.rs @@ -15,6 +15,16 @@ impl Length { 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) @@ -35,9 +45,14 @@ impl Length { Self::with_unit(inches, LengthUnit::In) } - /// Create a length from a number of raw units. - pub const fn raw(raw: f64) -> Self { - Self(Scalar(raw)) + /// 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 unit. + pub fn to_unit(self, unit: LengthUnit) -> f64 { + self.to_raw() / unit.raw_scale() } /// Convert this to a number of points. @@ -60,36 +75,6 @@ impl Length { self.to_unit(LengthUnit::In) } - /// Get the value of this length in raw units. - pub const fn to_raw(self) -> f64 { - (self.0).0 - } - - /// Create a length from a value in a unit. - pub fn with_unit(val: f64, unit: LengthUnit) -> Self { - Self(Scalar(val * unit.raw_scale())) - } - - /// Get the value of this length in unit. - pub fn to_unit(self, unit: LengthUnit) -> f64 { - self.to_raw() / unit.raw_scale() - } - - /// Whether the length is zero. - pub fn is_zero(self) -> bool { - self.to_raw() == 0.0 - } - - /// Whether the length is finite. - pub fn is_finite(self) -> bool { - self.to_raw().is_finite() - } - - /// Whether the length is infinite. - pub fn is_infinite(self) -> bool { - self.to_raw().is_infinite() - } - /// The absolute value of the this length. pub fn abs(self) -> Self { Self::raw(self.to_raw().abs()) @@ -137,6 +122,16 @@ impl Length { } } +impl Numeric for Length { + fn zero() -> Self { + Self::zero() + } + + fn is_finite(self) -> bool { + self.0.is_finite() + } +} + impl Debug for Length { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}pt", round_2(self.to_pt())) diff --git a/src/geom/mod.rs b/src/geom/mod.rs index 8d7759b4b..bfb450a1a 100644 --- a/src/geom/mod.rs +++ b/src/geom/mod.rs @@ -60,6 +60,30 @@ pub trait Get { } } +/// A numeric type. +pub trait Numeric: + Sized + + Debug + + Copy + + PartialEq + + Neg + + Add + + Sub + + Mul + + Div +{ + /// The identity element. + fn zero() -> Self; + + /// Whether `self` is the identity element. + fn is_zero(self) -> bool { + self == Self::zero() + } + + /// Whether `self` contains only finite parts. + fn is_finite(self) -> bool; +} + /// Round a float to two decimal places. fn round_2(value: f64) -> f64 { (value * 100.0).round() / 100.0 diff --git a/src/geom/paint.rs b/src/geom/paint.rs index 8dee363cc..3660d5282 100644 --- a/src/geom/paint.rs +++ b/src/geom/paint.rs @@ -1,4 +1,3 @@ -use std::fmt::Display; use std::str::FromStr; use syntect::highlighting::Color as SynColor; @@ -103,7 +102,7 @@ impl RgbaColor { } impl FromStr for RgbaColor { - type Err = RgbaError; + type Err = &'static str; /// Constructs a new color from hex strings like the following: /// - `#aef` (shorthand, with leading hashtag), @@ -113,8 +112,8 @@ impl FromStr for RgbaColor { /// The hashtag is optional and both lower and upper case are fine. fn from_str(hex_str: &str) -> Result { let hex_str = hex_str.strip_prefix('#').unwrap_or(hex_str); - if !hex_str.is_ascii() { - return Err(RgbaError); + if hex_str.chars().any(|c| !c.is_ascii_hexdigit()) { + return Err("string contains non-hexadecimal letters"); } let len = hex_str.len(); @@ -123,7 +122,7 @@ impl FromStr for RgbaColor { let alpha = len == 4 || len == 8; if !long && !short { - return Err(RgbaError); + return Err("string has wrong length"); } let mut values: [u8; 4] = [255; 4]; @@ -133,7 +132,7 @@ impl FromStr for RgbaColor { let pos = elem * item_len; let item = &hex_str[pos .. (pos + item_len)]; - values[elem] = u8::from_str_radix(item, 16).map_err(|_| RgbaError)?; + values[elem] = u8::from_str_radix(item, 16).unwrap(); if short { // Duplicate number for shorthand notation, i.e. `a` -> `aa` @@ -169,18 +168,6 @@ impl Debug for RgbaColor { } } -/// The error when parsing an [`RgbaColor`] from a string fails. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct RgbaError; - -impl Display for RgbaError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("invalid hex string") - } -} - -impl std::error::Error for RgbaError {} - /// An 8-bit CMYK color. #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct CmykColor { @@ -268,13 +255,14 @@ mod tests { #[test] fn test_parse_invalid_colors() { #[track_caller] - fn test(hex: &str) { - assert_eq!(RgbaColor::from_str(hex), Err(RgbaError)); + fn test(hex: &str, message: &str) { + assert_eq!(RgbaColor::from_str(hex), Err(message)); } - test("12345"); - test("a5"); - test("14B2AH"); - test("f075ff011"); + test("a5", "string has wrong length"); + test("12345", "string has wrong length"); + test("f075ff011", "string has wrong length"); + test("hmmm", "string contains non-hexadecimal letters"); + test("14B2AH", "string contains non-hexadecimal letters"); } } diff --git a/src/geom/point.rs b/src/geom/point.rs index 6d77507b4..afce68ba3 100644 --- a/src/geom/point.rs +++ b/src/geom/point.rs @@ -35,17 +35,22 @@ impl Point { Self { x: Length::zero(), y } } - /// Whether both components are zero. - pub fn is_zero(self) -> bool { - self.x.is_zero() && self.y.is_zero() + /// Transform the point with the given transformation. + pub fn transform(self, ts: Transform) -> Self { + Self::new( + ts.sx.resolve(self.x) + ts.kx.resolve(self.y) + ts.tx, + ts.ky.resolve(self.x) + ts.sy.resolve(self.y) + ts.ty, + ) + } +} + +impl Numeric for Point { + fn zero() -> Self { + Self::zero() } - /// Transform the point with the given transformation. - pub fn transform(self, transform: Transform) -> Self { - Self::new( - transform.sx.resolve(self.x) + transform.kx.resolve(self.y) + transform.tx, - transform.ky.resolve(self.x) + transform.sy.resolve(self.y) + transform.ty, - ) + fn is_finite(self) -> bool { + self.x.is_finite() && self.y.is_finite() } } diff --git a/src/geom/ratio.rs b/src/geom/ratio.rs index d035c33e7..7dca53c2c 100644 --- a/src/geom/ratio.rs +++ b/src/geom/ratio.rs @@ -28,16 +28,6 @@ impl Ratio { (self.0).0 } - /// Resolve this relative to the given `length`. - pub fn resolve(self, length: Length) -> Length { - // We don't want NaNs. - if length.is_infinite() { - Length::zero() - } else { - self.get() * length - } - } - /// Whether the ratio is zero. pub fn is_zero(self) -> bool { self.0 == 0.0 @@ -52,6 +42,12 @@ impl Ratio { pub fn abs(self) -> Self { Self::new(self.get().abs()) } + + /// Resolve this relative to the given `whole`. + pub fn resolve(self, whole: T) -> T { + let resolved = whole * self.get(); + if resolved.is_finite() { resolved } else { T::zero() } + } } impl Debug for Ratio { diff --git a/src/geom/relative.rs b/src/geom/relative.rs index fa2ec7fcc..8e8897e77 100644 --- a/src/geom/relative.rs +++ b/src/geom/relative.rs @@ -1,65 +1,60 @@ use super::*; -/// A relative length. +/// A value that is composed of a relative and an absolute part. #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Relative { +pub struct Relative { /// The relative part. pub rel: Ratio, /// The absolute part. - pub abs: Length, + pub abs: T, } -impl Relative { - /// The zero relative length. - pub const fn zero() -> Self { - Self { rel: Ratio::zero(), abs: Length::zero() } +impl Relative { + /// The zero relative. + pub fn zero() -> Self { + Self { rel: Ratio::zero(), abs: T::zero() } } - /// A relative length with a ratio of `100%` and no absolute part. - pub const fn one() -> Self { - Self { rel: Ratio::one(), abs: Length::zero() } + /// A relative with a ratio of `100%` and no absolute part. + pub fn one() -> Self { + Self { rel: Ratio::one(), abs: T::zero() } } - /// Create a new relative length from its parts. - pub const fn new(rel: Ratio, abs: Length) -> Self { + /// Create a new relative from its parts. + pub fn new(rel: Ratio, abs: T) -> Self { Self { rel, abs } } - /// Resolve this length relative to the given `length`. - pub fn resolve(self, length: Length) -> Length { - self.rel.resolve(length) + self.abs - } - /// Whether both parts are zero. pub fn is_zero(self) -> bool { self.rel.is_zero() && self.abs.is_zero() } - /// Whether there is a relative part. - pub fn is_relative(self) -> bool { - !self.rel.is_zero() + /// Resolve this relative to the given `whole`. + pub fn resolve(self, whole: T) -> T { + self.rel.resolve(whole) + self.abs } } -impl Debug for Relative { +impl Debug for Relative { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{:?} + {:?}", self.rel, self.abs) } } -impl From for Relative { - fn from(abs: Length) -> Self { +impl From for Relative { + fn from(abs: T) -> Self { Self { rel: Ratio::zero(), abs } } } -impl From for Relative { +impl From for Relative { fn from(rel: Ratio) -> Self { - Self { rel, abs: Length::zero() } + Self { rel, abs: T::zero() } } } -impl Neg for Relative { +impl Neg for Relative { type Output = Self; fn neg(self) -> Self { @@ -67,10 +62,10 @@ impl Neg for Relative { } } -impl Add for Relative { +impl Add for Relative { type Output = Self; - fn add(self, other: Self) -> Self { + fn add(self, other: Self) -> Self::Output { Self { rel: self.rel + other.rel, abs: self.abs + other.abs, @@ -78,66 +73,18 @@ impl Add for Relative { } } -impl Add for Length { - type Output = Relative; - - fn add(self, other: Ratio) -> Relative { - Relative { rel: other, abs: self } - } -} - -impl Add for Ratio { - type Output = Relative; - - fn add(self, other: Length) -> Relative { - other + self - } -} - -impl Add for Relative { +impl Sub for Relative { type Output = Self; - fn add(self, other: Length) -> Self { - Self { rel: self.rel, abs: self.abs + other } + fn sub(self, other: Self) -> Self::Output { + self + -other } } -impl Add for Length { - type Output = Relative; - - fn add(self, other: Relative) -> Relative { - other + self - } -} - -impl Add for Relative { +impl Mul for Relative { type Output = Self; - fn add(self, other: Ratio) -> Self { - Self { rel: self.rel + other, abs: self.abs } - } -} - -impl Add for Ratio { - type Output = Relative; - - fn add(self, other: Relative) -> Relative { - other + self - } -} - -sub_impl!(Relative - Relative -> Relative); -sub_impl!(Length - Ratio -> Relative); -sub_impl!(Ratio - Length -> Relative); -sub_impl!(Relative - Length -> Relative); -sub_impl!(Length - Relative -> Relative); -sub_impl!(Relative - Ratio -> Relative); -sub_impl!(Ratio - Relative -> Relative); - -impl Mul for Relative { - type Output = Self; - - fn mul(self, other: f64) -> Self { + fn mul(self, other: f64) -> Self::Output { Self { rel: self.rel * other, abs: self.abs * other, @@ -145,18 +92,18 @@ impl Mul for Relative { } } -impl Mul for f64 { - type Output = Relative; +impl Mul> for f64 { + type Output = Relative; - fn mul(self, other: Relative) -> Relative { + fn mul(self, other: Relative) -> Self::Output { other * self } } -impl Div for Relative { +impl Div for Relative { type Output = Self; - fn div(self, other: f64) -> Self { + fn div(self, other: f64) -> Self::Output { Self { rel: self.rel / other, abs: self.abs / other, @@ -164,11 +111,50 @@ impl Div for Relative { } } -assign_impl!(Relative += Relative); -assign_impl!(Relative += Length); -assign_impl!(Relative += Ratio); -assign_impl!(Relative -= Relative); -assign_impl!(Relative -= Length); -assign_impl!(Relative -= Ratio); -assign_impl!(Relative *= f64); -assign_impl!(Relative /= f64); +impl AddAssign for Relative { + fn add_assign(&mut self, other: Self) { + *self = *self + other; + } +} + +impl SubAssign for Relative { + fn sub_assign(&mut self, other: Self) { + *self = *self - other; + } +} + +impl MulAssign for Relative { + fn mul_assign(&mut self, other: f64) { + *self = *self * other; + } +} + +impl DivAssign for Relative { + fn div_assign(&mut self, other: f64) { + *self = *self * other; + } +} + +impl Add for Ratio { + type Output = Relative; + + fn add(self, other: T) -> Self::Output { + Relative::from(self) + Relative::from(other) + } +} + +impl Add for Relative { + type Output = Self; + + fn add(self, other: T) -> Self::Output { + self + Relative::from(other) + } +} + +impl Add for Relative { + type Output = Self; + + fn add(self, other: Ratio) -> Self::Output { + self + Relative::from(other) + } +} diff --git a/src/geom/scalar.rs b/src/geom/scalar.rs index 1435654dd..91225a2b4 100644 --- a/src/geom/scalar.rs +++ b/src/geom/scalar.rs @@ -6,6 +6,16 @@ use super::*; #[derive(Default, Copy, Clone)] pub struct Scalar(pub f64); +impl Numeric for Scalar { + fn zero() -> Self { + Self(0.0) + } + + fn is_finite(self) -> bool { + self.0.is_finite() + } +} + impl From for Scalar { fn from(float: f64) -> Self { Self(float) diff --git a/src/geom/sides.rs b/src/geom/sides.rs index 45420c188..4539728fc 100644 --- a/src/geom/sides.rs +++ b/src/geom/sides.rs @@ -43,7 +43,7 @@ where } } -impl Sides { +impl Sides> { /// Resolve the sides relative to the given `size`. pub fn resolve(self, size: Size) -> Sides { Sides { diff --git a/src/geom/spec.rs b/src/geom/spec.rs index 31f93a657..3fe1793af 100644 --- a/src/geom/spec.rs +++ b/src/geom/spec.rs @@ -201,7 +201,7 @@ pub type Size = Spec; impl Size { /// The zero value. - pub fn zero() -> Self { + pub const fn zero() -> Self { Self { x: Length::zero(), y: Length::zero() } } @@ -210,27 +210,22 @@ impl Size { self.x.fits(other.x) && self.y.fits(other.y) } - /// Whether both components are zero. - pub fn is_zero(self) -> bool { - self.x.is_zero() && self.y.is_zero() - } - - /// Whether both components are finite. - pub fn is_finite(self) -> bool { - self.x.is_finite() && self.y.is_finite() - } - - /// Whether any of the two components is infinite. - pub fn is_infinite(self) -> bool { - self.x.is_infinite() || self.y.is_infinite() - } - /// 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; diff --git a/src/geom/transform.rs b/src/geom/transform.rs index 8d64ebcf4..c0a06e33c 100644 --- a/src/geom/transform.rs +++ b/src/geom/transform.rs @@ -24,18 +24,18 @@ impl Transform { } } - /// A translation transform. - pub const fn translation(tx: Length, ty: Length) -> Self { + /// A translate transform. + pub const fn translate(tx: Length, ty: Length) -> Self { Self { tx, ty, ..Self::identity() } } - /// A scaling transform. + /// A scale transform. pub const fn scale(sx: Ratio, sy: Ratio) -> Self { Self { sx, sy, ..Self::identity() } } - /// A rotation transform. - pub fn rotation(angle: Angle) -> Self { + /// A rotate transform. + pub fn rotate(angle: Angle) -> Self { let cos = Ratio::new(angle.cos()); let sin = Ratio::new(angle.sin()); Self { diff --git a/src/library/graphics/line.rs b/src/library/graphics/line.rs index 1ca25bd9d..571506c12 100644 --- a/src/library/graphics/line.rs +++ b/src/library/graphics/line.rs @@ -3,8 +3,10 @@ use crate::library::prelude::*; /// Display a line without affecting the layout. #[derive(Debug, Hash)] pub struct LineNode { - origin: Spec, - delta: Spec, + /// Where the line starts. + origin: Spec>, + /// The offset from the `origin` where the line ends. + delta: Spec>, } #[node] @@ -15,14 +17,15 @@ impl LineNode { pub const THICKNESS: Length = Length::pt(1.0); fn construct(_: &mut Context, args: &mut Args) -> TypResult { - let origin = args.named::>("origin")?.unwrap_or_default(); - let delta = match args.named::>("to")? { + let origin = args.named("origin")?.unwrap_or_default(); + 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 angle = args.named::("angle")?.unwrap_or_default(); + let length = args + .named::>("length")? + .unwrap_or(Length::cm(1.0).into()); + let angle = args.named::("angle")?.unwrap_or_default(); let x = angle.cos() * length; let y = angle.sin() * length; diff --git a/src/library/graphics/shape.rs b/src/library/graphics/shape.rs index 3f338d1a9..9faa4c52a 100644 --- a/src/library/graphics/shape.rs +++ b/src/library/graphics/shape.rs @@ -28,7 +28,7 @@ impl ShapeNode { /// The stroke's thickness. pub const THICKNESS: Length = Length::pt(1.0); /// How much to pad the shape's content. - pub const PADDING: Relative = Relative::zero(); + pub const PADDING: Relative = Relative::zero(); fn construct(_: &mut Context, args: &mut Args) -> TypResult { let size = match S { diff --git a/src/library/graphics/transform.rs b/src/library/graphics/transform.rs index 1ce91f286..eb419a7e3 100644 --- a/src/library/graphics/transform.rs +++ b/src/library/graphics/transform.rs @@ -29,11 +29,11 @@ impl TransformNode { MOVE => { let tx = args.named("x")?.unwrap_or_default(); let ty = args.named("y")?.unwrap_or_default(); - Transform::translation(tx, ty) + Transform::translate(tx, ty) } ROTATE => { let angle = args.named_or_find("angle")?.unwrap_or_default(); - Transform::rotation(angle) + Transform::rotate(angle) } SCALE | _ => { let all = args.find()?; @@ -62,9 +62,9 @@ impl Layout for TransformNode { for frame in &mut frames { let Spec { x, y } = origin.zip(frame.size).map(|(o, s)| o.resolve(s)); - let transform = Transform::translation(x, y) + let transform = Transform::translate(x, y) .pre_concat(self.transform) - .pre_concat(Transform::translation(-x, -y)); + .pre_concat(Transform::translate(-x, -y)); Arc::make_mut(frame).transform(transform); } diff --git a/src/library/layout/columns.rs b/src/library/layout/columns.rs index 56e55d578..1cb45c37f 100644 --- a/src/library/layout/columns.rs +++ b/src/library/layout/columns.rs @@ -14,7 +14,7 @@ pub struct ColumnsNode { #[node] impl ColumnsNode { /// The size of the gutter space between each column. - pub const GUTTER: Relative = Ratio::new(0.04).into(); + pub const GUTTER: Relative = Ratio::new(0.04).into(); fn construct(_: &mut Context, args: &mut Args) -> TypResult { Ok(Content::block(Self { @@ -33,7 +33,7 @@ impl Layout for ColumnsNode { ) -> TypResult>> { // Separating the infinite space into infinite columns does not make // much sense. - if regions.first.x.is_infinite() { + if !regions.first.x.is_finite() { return self.child.layout(ctx, regions, styles); } diff --git a/src/library/layout/grid.rs b/src/library/layout/grid.rs index ee485bd2f..b1e5e54c3 100644 --- a/src/library/layout/grid.rs +++ b/src/library/layout/grid.rs @@ -58,7 +58,7 @@ pub enum TrackSizing { Auto, /// A track size specified in absolute terms and relative to the parent's /// size. - Relative(Relative), + Relative(Relative), /// A track size specified as a fraction of the remaining free space in the /// parent. Fractional(Fraction), @@ -422,7 +422,7 @@ impl<'a> GridLayouter<'a> { fn layout_relative_row( &mut self, ctx: &mut Context, - v: Relative, + v: Relative, y: usize, ) -> TypResult<()> { let resolved = v.resolve(self.regions.base.y); diff --git a/src/library/layout/pad.rs b/src/library/layout/pad.rs index 1ec5f1249..b7470540d 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.resolve(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 abe1786fd..37a87ae22 100644 --- a/src/library/layout/page.rs +++ b/src/library/layout/page.rs @@ -16,13 +16,13 @@ impl PageNode { /// Whether the page is flipped into landscape orientation. pub const FLIPPED: bool = false; /// The left margin. - pub const LEFT: Smart = Smart::Auto; + pub const LEFT: Smart> = Smart::Auto; /// The right margin. - pub const RIGHT: Smart = Smart::Auto; + pub const RIGHT: Smart> = Smart::Auto; /// The top margin. - pub const TOP: Smart = Smart::Auto; + pub const TOP: Smart> = Smart::Auto; /// The bottom margin. - pub const BOTTOM: Smart = Smart::Auto; + pub const BOTTOM: Smart> = Smart::Auto; /// The page's background color. pub const FILL: Option = None; /// How many columns the page has. @@ -85,7 +85,7 @@ impl PageNode { } let mut min = width.min(height); - if min.is_infinite() { + if !min.is_finite() { min = Paper::A4.width(); } @@ -115,7 +115,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(Numeric::is_finite)); let mut frames = child.layout(ctx, ®ions, styles)?; let header = styles.get(Self::HEADER); @@ -133,7 +133,7 @@ impl PageNode { let pos = Point::new(padding.left, y); let w = size.x - padding.left - padding.right; let area = Size::new(w, h); - let pod = Regions::one(area, area, area.map(Length::is_finite)); + let pod = Regions::one(area, area, area.map(Numeric::is_finite)); let sub = Layout::layout(&content, ctx, &pod, styles)?.remove(0); Arc::make_mut(frame).push_frame(pos, sub); } diff --git a/src/library/layout/spacing.rs b/src/library/layout/spacing.rs index 633093e9b..e9837ef5e 100644 --- a/src/library/layout/spacing.rs +++ b/src/library/layout/spacing.rs @@ -24,7 +24,7 @@ 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(Relative), /// Spacing specified as a fraction of the remaining free space in the parent. Fractional(Fraction), } diff --git a/src/library/mod.rs b/src/library/mod.rs index af9ab575f..7c5a519f7 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -167,16 +167,13 @@ castable! { } castable! { - Spec, + Spec>, Expected: "array of two relative lengths", Value::Array(array) => { - match array.as_slice() { - [a, b] => { - let a = a.clone().cast::()?; - let b = b.clone().cast::()?; - Spec::new(a, b) - }, - _ => return Err("point array must contain exactly two entries".to_string()), + let mut iter = array.into_iter(); + match (iter.next(), iter.next(), iter.next()) { + (Some(a), Some(b), None) => Spec::new(a.cast()?, b.cast()?), + _ => Err("point array must contain exactly two entries")?, } }, } diff --git a/src/library/structure/list.rs b/src/library/structure/list.rs index 532ec6258..c58e8648a 100644 --- a/src/library/structure/list.rs +++ b/src/library/structure/list.rs @@ -34,11 +34,11 @@ impl ListNode { #[property(referenced)] pub const LABEL: Label = Label::Default; /// The spacing between the list items of a non-wide list. - pub const SPACING: Relative = Relative::zero(); + pub const SPACING: Relative = Relative::zero(); /// The indentation of each item's label. - pub const INDENT: Relative = Ratio::new(0.0).into(); + pub const INDENT: Relative = Ratio::new(0.0).into(); /// The space between the label and the body of each item. - pub const BODY_INDENT: Relative = Ratio::new(0.5).into(); + pub const BODY_INDENT: Relative = Ratio::new(0.5).into(); /// The extra padding above the list. pub const ABOVE: Length = Length::zero(); /// The extra padding below the list. diff --git a/src/library/structure/table.rs b/src/library/structure/table.rs index 9317e43f1..e01ae9081 100644 --- a/src/library/structure/table.rs +++ b/src/library/structure/table.rs @@ -23,7 +23,7 @@ impl TableNode { /// The stroke's thickness. pub const THICKNESS: Length = Length::pt(1.0); /// How much to pad the cells's content. - pub const PADDING: Relative = Length::pt(5.0).into(); + pub const PADDING: Relative = Length::pt(5.0).into(); fn construct(_: &mut Context, args: &mut Args) -> TypResult { let columns = args.named("columns")?.unwrap_or_default(); diff --git a/src/library/text/deco.rs b/src/library/text/deco.rs index a81f0374b..da1a11418 100644 --- a/src/library/text/deco.rs +++ b/src/library/text/deco.rs @@ -26,13 +26,13 @@ impl DecoNode { /// Thickness of the line's strokes (dependent on scaled font size), read /// from the font tables if `None`. #[property(shorthand)] - pub const THICKNESS: Option = None; + pub const THICKNESS: Option> = None; /// Position of the line relative to the baseline (dependent on scaled font /// size), read from the font tables if `None`. - pub const OFFSET: Option = None; + pub const OFFSET: Option> = None; /// Amount that the line will be longer or shorter than its associated text /// (dependent on scaled font size). - pub const EXTENT: Relative = Relative::zero(); + pub const EXTENT: Relative = Relative::zero(); /// Whether the line skips sections in which it would collide /// with the glyphs. Does not apply to strikethrough. pub const EVADE: bool = true; @@ -66,9 +66,9 @@ impl Show for DecoNode { pub struct Decoration { pub line: DecoLine, pub stroke: Option, - pub thickness: Option, - pub offset: Option, - pub extent: Relative, + pub thickness: Option>, + pub offset: Option>, + pub extent: Relative, pub evade: bool, } diff --git a/src/library/text/mod.rs b/src/library/text/mod.rs index 975a48059..4a139fb3d 100644 --- a/src/library/text/mod.rs +++ b/src/library/text/mod.rs @@ -188,7 +188,7 @@ castable! { /// The size of text. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct FontSize(pub Relative); +pub struct FontSize(pub Relative); impl Fold for FontSize { type Output = Length; diff --git a/src/library/text/par.rs b/src/library/text/par.rs index a05aff446..2d31cd116 100644 --- a/src/library/text/par.rs +++ b/src/library/text/par.rs @@ -42,11 +42,11 @@ impl ParNode { /// will will be hyphenated if and only if justification is enabled. pub const HYPHENATE: Smart = Smart::Auto; /// The spacing between lines (dependent on scaled font size). - pub const LEADING: Relative = Ratio::new(0.65).into(); + pub const LEADING: Relative = Ratio::new(0.65).into(); /// The extra spacing between paragraphs (dependent on scaled font size). - pub const SPACING: Relative = Ratio::new(0.55).into(); + pub const SPACING: Relative = Ratio::new(0.55).into(); /// The indent the first line of a consecutive paragraph should have. - pub const INDENT: Relative = Relative::zero(); + pub const INDENT: Relative = Relative::zero(); fn construct(_: &mut Context, args: &mut Args) -> TypResult { // The paragraph constructor is special: It doesn't create a paragraph diff --git a/src/library/utility/color.rs b/src/library/utility/color.rs index 409af1772..75410380f 100644 --- a/src/library/utility/color.rs +++ b/src/library/utility/color.rs @@ -8,7 +8,7 @@ pub fn rgb(_: &mut Context, args: &mut Args) -> TypResult { if let Some(string) = args.find::>()? { match RgbaColor::from_str(&string.v) { Ok(color) => color, - Err(_) => bail!(string.span, "invalid hex string"), + Err(msg) => bail!(string.span, msg), } } else { struct Component(u8); diff --git a/tests/typ/utility/color.typ b/tests/typ/utility/color.typ index fd60982f8..81b67ae93 100644 --- a/tests/typ/utility/color.typ +++ b/tests/typ/utility/color.typ @@ -20,7 +20,7 @@ #test(rgb(-30, 15, 50)) --- -// Error: 6-11 invalid hex string +// Error: 6-11 string contains non-hexadecimal letters #rgb("lol") ---