Make Relative generic

This commit is contained in:
Laurenz 2022-04-07 18:04:29 +02:00
parent 1192132dc0
commit 4bb6240b40
35 changed files with 318 additions and 294 deletions

View File

@ -8,7 +8,9 @@ use std::sync::Arc;
use super::{Barrier, StyleChain}; use super::{Barrier, StyleChain};
use crate::diag::TypResult; use crate::diag::TypResult;
use crate::frame::{Element, Frame, Geometry, Shape, Stroke}; 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::graphics::MoveNode;
use crate::library::layout::{AlignNode, PadNode}; use crate::library::layout::{AlignNode, PadNode};
use crate::util::Prehashed; use crate::util::Prehashed;
@ -161,7 +163,7 @@ impl LayoutNode {
} }
/// Force a size for this node. /// Force a size for this node.
pub fn sized(self, sizing: Spec<Option<Relative>>) -> Self { pub fn sized(self, sizing: Spec<Option<Relative<Length>>>) -> Self {
if sizing.any(Option::is_some) { if sizing.any(Option::is_some) {
SizedNode { sizing, child: self }.pack() SizedNode { sizing, child: self }.pack()
} else { } else {
@ -189,7 +191,7 @@ impl LayoutNode {
} }
/// Pad this node at the sides. /// Pad this node at the sides.
pub fn padded(self, padding: Sides<Relative>) -> Self { pub fn padded(self, padding: Sides<Relative<Length>>) -> Self {
if !padding.left.is_zero() if !padding.left.is_zero()
|| !padding.top.is_zero() || !padding.top.is_zero()
|| !padding.right.is_zero() || !padding.right.is_zero()
@ -205,7 +207,7 @@ impl LayoutNode {
pub fn moved(self, offset: Point) -> Self { pub fn moved(self, offset: Point) -> Self {
if !offset.is_zero() { if !offset.is_zero() {
MoveNode { MoveNode {
transform: Transform::translation(offset.x, offset.y), transform: Transform::translate(offset.x, offset.y),
child: self, child: self,
} }
.pack() .pack()
@ -292,7 +294,7 @@ impl Layout for EmptyNode {
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
struct SizedNode { struct SizedNode {
/// How to size the node horizontally and vertically. /// How to size the node horizontally and vertically.
sizing: Spec<Option<Relative>>, sizing: Spec<Option<Relative<Length>>>,
/// The node to be sized. /// The node to be sized.
child: LayoutNode, child: LayoutNode,
} }

View File

@ -2,7 +2,7 @@ use std::cmp::Ordering;
use super::{Dynamic, StrExt, Value}; use super::{Dynamic, StrExt, Value};
use crate::diag::StrResult; use crate::diag::StrResult;
use crate::geom::{Align, Spec, SpecAxis}; use crate::geom::{Align, Numeric, Spec, SpecAxis};
use Value::*; use Value::*;
/// Bail with a type mismatch error. /// Bail with a type mismatch error.
@ -66,12 +66,12 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
(Angle(a), Angle(b)) => Angle(a + b), (Angle(a), Angle(b)) => Angle(a + b),
(Length(a), Length(b)) => Length(a + b), (Length(a), Length(b)) => Length(a + b),
(Length(a), Ratio(b)) => Relative(a + b), (Length(a), Ratio(b)) => Relative(b + a),
(Length(a), Relative(b)) => Relative(a + b), (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), 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), Length(b)) => Relative(a + b),
(Relative(a), Ratio(b)) => Relative(a + b), (Relative(a), Ratio(b)) => Relative(a + b),
@ -123,15 +123,15 @@ pub fn sub(lhs: Value, rhs: Value) -> StrResult<Value> {
(Angle(a), Angle(b)) => Angle(a - b), (Angle(a), Angle(b)) => Angle(a - b),
(Length(a), Length(b)) => Length(a - b), (Length(a), Length(b)) => Length(a - b),
(Length(a), Ratio(b)) => Relative(a - b), (Length(a), Ratio(b)) => Relative(-b + a),
(Length(a), Relative(b)) => Relative(a - b), (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), 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), Length(b)) => Relative(a + -b),
(Relative(a), Ratio(b)) => Relative(a - b), (Relative(a), Ratio(b)) => Relative(a + -b),
(Relative(a), Relative(b)) => Relative(a - b), (Relative(a), Relative(b)) => Relative(a - b),
(Fraction(a), Fraction(b)) => Fraction(a - b), (Fraction(a), Fraction(b)) => Fraction(a - b),

View File

@ -31,7 +31,7 @@ pub enum Value {
/// A ratio: `50%`. /// A ratio: `50%`.
Ratio(Ratio), Ratio(Ratio),
/// A relative length, combination of a ratio and a length: `20% + 5cm`. /// A relative length, combination of a ratio and a length: `20% + 5cm`.
Relative(Relative), Relative(Relative<Length>),
/// A fraction: `1fr`. /// A fraction: `1fr`.
Fraction(Fraction), Fraction(Fraction),
/// A color value: `#f79143ff`. /// A color value: `#f79143ff`.
@ -549,7 +549,7 @@ primitive! { f64: "float", Float, Int(v) => v as f64 }
primitive! { Length: "length", Length } primitive! { Length: "length", Length }
primitive! { Angle: "angle", Angle } primitive! { Angle: "angle", Angle }
primitive! { Ratio: "ratio", Ratio } primitive! { Ratio: "ratio", Ratio }
primitive! { Relative: "relative length", Relative, Length(v) => v.into(), Ratio(v) => v.into() } primitive! { Relative<Length>: "relative length", Relative, Length(v) => v.into(), Ratio(v) => v.into() }
primitive! { Fraction: "fraction", Fraction } primitive! { Fraction: "fraction", Fraction }
primitive! { Color: "color", Color } primitive! { Color: "color", Color }
primitive! { EcoString: "string", Str } primitive! { EcoString: "string", Str }

View File

@ -17,7 +17,7 @@ use ttf_parser::{name_id, GlyphId, Tag};
use super::subset::subset; use super::subset::subset;
use crate::font::{find_name, FaceId, FontStore}; use crate::font::{find_name, FaceId, FontStore};
use crate::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text}; 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::image::{Image, ImageId, ImageStore, RasterImage};
use crate::Context; use crate::Context;
@ -423,7 +423,7 @@ impl<'a> PageExporter<'a> {
} }
fn write_group(&mut self, pos: Point, group: &Group) { 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.save_state();
self.transform(translation.pre_concat(group.transform)); self.transform(translation.pre_concat(group.transform));

View File

@ -415,7 +415,7 @@ pub enum VerticalFontMetric {
Descender, Descender,
/// An font-size dependent distance from the baseline (positive goes up, negative /// An font-size dependent distance from the baseline (positive goes up, negative
/// down). /// down).
Relative(Relative), Relative(Relative<Length>),
} }
/// Properties of a single font face. /// Properties of a single font face.

View File

@ -4,7 +4,9 @@ use std::fmt::{self, Debug, Formatter};
use std::sync::Arc; use std::sync::Arc;
use crate::font::FaceId; 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; use crate::image::ImageId;
/// A finished layout with elements at fixed positions. /// A finished layout with elements at fixed positions.

View File

@ -10,6 +10,16 @@ impl Angle {
Self(Scalar(0.0)) 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. /// Create an angle from a number of radians.
pub fn rad(rad: f64) -> Self { pub fn rad(rad: f64) -> Self {
Self::with_unit(rad, AngularUnit::Rad) Self::with_unit(rad, AngularUnit::Rad)
@ -20,9 +30,14 @@ impl Angle {
Self::with_unit(deg, AngularUnit::Deg) Self::with_unit(deg, AngularUnit::Deg)
} }
/// Create an angle from a number of raw units. /// Get the value of this angle in raw units.
pub const fn raw(raw: f64) -> Self { pub const fn to_raw(self) -> f64 {
Self(Scalar(raw)) (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. /// Convert this to a number of radians.
@ -35,9 +50,9 @@ impl Angle {
self.to_unit(AngularUnit::Deg) self.to_unit(AngularUnit::Deg)
} }
/// Get the value of this angle in raw units. /// The absolute value of the this angle.
pub const fn to_raw(self) -> f64 { pub fn abs(self) -> Self {
(self.0).0 Self::raw(self.to_raw().abs())
} }
/// Get the sine of this angle in radians. /// Get the sine of this angle in radians.
@ -49,20 +64,15 @@ impl Angle {
pub fn cos(self) -> f64 { pub fn cos(self) -> f64 {
self.to_rad().cos() self.to_rad().cos()
} }
}
/// Create an angle from a value in a unit. impl Numeric for Angle {
pub fn with_unit(val: f64, unit: AngularUnit) -> Self { fn zero() -> Self {
Self(Scalar(val * unit.raw_scale())) Self::zero()
} }
/// Get the value of this length in unit. fn is_finite(self) -> bool {
pub fn to_unit(self, unit: AngularUnit) -> f64 { self.0.is_finite()
self.to_raw() / unit.raw_scale()
}
/// The absolute value of the this angle.
pub fn abs(self) -> Self {
Self::raw(self.to_raw().abs())
} }
} }
@ -132,6 +142,7 @@ impl Sum for Angle {
Self(iter.map(|s| s.0).sum()) Self(iter.map(|s| s.0).sum())
} }
} }
/// Different units of angular measurement. /// Different units of angular measurement.
#[derive(Copy, Clone, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub enum AngularUnit { pub enum AngularUnit {

View File

@ -32,19 +32,24 @@ impl Em {
Self(Scalar(length / font_size)) 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. /// The number of em units.
pub const fn get(self) -> f64 { pub const fn get(self) -> f64 {
(self.0).0 (self.0).0
} }
/// Whether the length is zero. /// Convert to a length at the given font size.
pub fn is_zero(self) -> bool { pub fn resolve(self, font_size: Length) -> Length {
self.0 == 0.0 self.get() * font_size
}
}
impl Numeric for Em {
fn zero() -> Self {
Self::zero()
}
fn is_finite(self) -> bool {
self.0.is_finite()
} }
} }

View File

@ -25,11 +25,6 @@ impl Fraction {
(self.0).0 (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. /// The absolute value of the this fraction.
pub fn abs(self) -> Self { pub fn abs(self) -> Self {
Self::new(self.get().abs()) 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 { impl Debug for Fraction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}fr", round_2(self.get())) write!(f, "{}fr", round_2(self.get()))

View File

@ -15,6 +15,16 @@ impl Length {
Self(Scalar(f64::INFINITY)) 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. /// Create a length from a number of points.
pub fn pt(pt: f64) -> Self { pub fn pt(pt: f64) -> Self {
Self::with_unit(pt, LengthUnit::Pt) Self::with_unit(pt, LengthUnit::Pt)
@ -35,9 +45,14 @@ impl Length {
Self::with_unit(inches, LengthUnit::In) Self::with_unit(inches, LengthUnit::In)
} }
/// Create a length from a number of raw units. /// Get the value of this length in raw units.
pub const fn raw(raw: f64) -> Self { pub const fn to_raw(self) -> f64 {
Self(Scalar(raw)) (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. /// Convert this to a number of points.
@ -60,36 +75,6 @@ impl Length {
self.to_unit(LengthUnit::In) 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. /// The absolute value of the this length.
pub fn abs(self) -> Self { pub fn abs(self) -> Self {
Self::raw(self.to_raw().abs()) 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 { impl Debug for Length {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}pt", round_2(self.to_pt())) write!(f, "{}pt", round_2(self.to_pt()))

View File

@ -60,6 +60,30 @@ pub trait Get<Index> {
} }
} }
/// A numeric type.
pub trait Numeric:
Sized
+ Debug
+ Copy
+ PartialEq
+ Neg<Output = Self>
+ Add<Output = Self>
+ Sub<Output = Self>
+ Mul<f64, Output = Self>
+ Div<f64, Output = Self>
{
/// 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. /// Round a float to two decimal places.
fn round_2(value: f64) -> f64 { fn round_2(value: f64) -> f64 {
(value * 100.0).round() / 100.0 (value * 100.0).round() / 100.0

View File

@ -1,4 +1,3 @@
use std::fmt::Display;
use std::str::FromStr; use std::str::FromStr;
use syntect::highlighting::Color as SynColor; use syntect::highlighting::Color as SynColor;
@ -103,7 +102,7 @@ impl RgbaColor {
} }
impl FromStr for RgbaColor { impl FromStr for RgbaColor {
type Err = RgbaError; type Err = &'static str;
/// Constructs a new color from hex strings like the following: /// Constructs a new color from hex strings like the following:
/// - `#aef` (shorthand, with leading hashtag), /// - `#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. /// The hashtag is optional and both lower and upper case are fine.
fn from_str(hex_str: &str) -> Result<Self, Self::Err> { fn from_str(hex_str: &str) -> Result<Self, Self::Err> {
let hex_str = hex_str.strip_prefix('#').unwrap_or(hex_str); let hex_str = hex_str.strip_prefix('#').unwrap_or(hex_str);
if !hex_str.is_ascii() { if hex_str.chars().any(|c| !c.is_ascii_hexdigit()) {
return Err(RgbaError); return Err("string contains non-hexadecimal letters");
} }
let len = hex_str.len(); let len = hex_str.len();
@ -123,7 +122,7 @@ impl FromStr for RgbaColor {
let alpha = len == 4 || len == 8; let alpha = len == 4 || len == 8;
if !long && !short { if !long && !short {
return Err(RgbaError); return Err("string has wrong length");
} }
let mut values: [u8; 4] = [255; 4]; let mut values: [u8; 4] = [255; 4];
@ -133,7 +132,7 @@ impl FromStr for RgbaColor {
let pos = elem * item_len; let pos = elem * item_len;
let item = &hex_str[pos .. (pos + 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 { if short {
// Duplicate number for shorthand notation, i.e. `a` -> `aa` // 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. /// An 8-bit CMYK color.
#[derive(Copy, Clone, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct CmykColor { pub struct CmykColor {
@ -268,13 +255,14 @@ mod tests {
#[test] #[test]
fn test_parse_invalid_colors() { fn test_parse_invalid_colors() {
#[track_caller] #[track_caller]
fn test(hex: &str) { fn test(hex: &str, message: &str) {
assert_eq!(RgbaColor::from_str(hex), Err(RgbaError)); assert_eq!(RgbaColor::from_str(hex), Err(message));
} }
test("12345"); test("a5", "string has wrong length");
test("a5"); test("12345", "string has wrong length");
test("14B2AH"); test("f075ff011", "string has wrong length");
test("f075ff011"); test("hmmm", "string contains non-hexadecimal letters");
test("14B2AH", "string contains non-hexadecimal letters");
} }
} }

View File

@ -35,17 +35,22 @@ impl Point {
Self { x: Length::zero(), y } Self { x: Length::zero(), y }
} }
/// Whether both components are zero. /// Transform the point with the given transformation.
pub fn is_zero(self) -> bool { pub fn transform(self, ts: Transform) -> Self {
self.x.is_zero() && self.y.is_zero() 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. fn is_finite(self) -> bool {
pub fn transform(self, transform: Transform) -> Self { self.x.is_finite() && self.y.is_finite()
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,
)
} }
} }

View File

@ -28,16 +28,6 @@ impl Ratio {
(self.0).0 (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. /// Whether the ratio is zero.
pub fn is_zero(self) -> bool { pub fn is_zero(self) -> bool {
self.0 == 0.0 self.0 == 0.0
@ -52,6 +42,12 @@ impl Ratio {
pub fn abs(self) -> Self { pub fn abs(self) -> Self {
Self::new(self.get().abs()) Self::new(self.get().abs())
} }
/// Resolve this relative to the given `whole`.
pub fn resolve<T: Numeric>(self, whole: T) -> T {
let resolved = whole * self.get();
if resolved.is_finite() { resolved } else { T::zero() }
}
} }
impl Debug for Ratio { impl Debug for Ratio {

View File

@ -1,65 +1,60 @@
use super::*; use super::*;
/// A relative length. /// A value that is composed of a relative and an absolute part.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Relative { pub struct Relative<T: Numeric> {
/// The relative part. /// The relative part.
pub rel: Ratio, pub rel: Ratio,
/// The absolute part. /// The absolute part.
pub abs: Length, pub abs: T,
} }
impl Relative { impl<T: Numeric> Relative<T> {
/// The zero relative length. /// The zero relative.
pub const fn zero() -> Self { pub fn zero() -> Self {
Self { rel: Ratio::zero(), abs: Length::zero() } Self { rel: Ratio::zero(), abs: T::zero() }
} }
/// A relative length with a ratio of `100%` and no absolute part. /// A relative with a ratio of `100%` and no absolute part.
pub const fn one() -> Self { pub fn one() -> Self {
Self { rel: Ratio::one(), abs: Length::zero() } Self { rel: Ratio::one(), abs: T::zero() }
} }
/// Create a new relative length from its parts. /// Create a new relative from its parts.
pub const fn new(rel: Ratio, abs: Length) -> Self { pub fn new(rel: Ratio, abs: T) -> Self {
Self { rel, abs } 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. /// Whether both parts are zero.
pub fn is_zero(self) -> bool { pub fn is_zero(self) -> bool {
self.rel.is_zero() && self.abs.is_zero() self.rel.is_zero() && self.abs.is_zero()
} }
/// Whether there is a relative part. /// Resolve this relative to the given `whole`.
pub fn is_relative(self) -> bool { pub fn resolve(self, whole: T) -> T {
!self.rel.is_zero() self.rel.resolve(whole) + self.abs
} }
} }
impl Debug for Relative { impl<T: Numeric> Debug for Relative<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:?} + {:?}", self.rel, self.abs) write!(f, "{:?} + {:?}", self.rel, self.abs)
} }
} }
impl From<Length> for Relative { impl<T: Numeric> From<T> for Relative<T> {
fn from(abs: Length) -> Self { fn from(abs: T) -> Self {
Self { rel: Ratio::zero(), abs } Self { rel: Ratio::zero(), abs }
} }
} }
impl From<Ratio> for Relative { impl<T: Numeric> From<Ratio> for Relative<T> {
fn from(rel: Ratio) -> Self { fn from(rel: Ratio) -> Self {
Self { rel, abs: Length::zero() } Self { rel, abs: T::zero() }
} }
} }
impl Neg for Relative { impl<T: Numeric> Neg for Relative<T> {
type Output = Self; type Output = Self;
fn neg(self) -> Self { fn neg(self) -> Self {
@ -67,10 +62,10 @@ impl Neg for Relative {
} }
} }
impl Add for Relative { impl<T: Numeric> Add for Relative<T> {
type Output = Self; type Output = Self;
fn add(self, other: Self) -> Self { fn add(self, other: Self) -> Self::Output {
Self { Self {
rel: self.rel + other.rel, rel: self.rel + other.rel,
abs: self.abs + other.abs, abs: self.abs + other.abs,
@ -78,66 +73,18 @@ impl Add for Relative {
} }
} }
impl Add<Ratio> for Length { impl<T: Numeric> Sub for Relative<T> {
type Output = Relative;
fn add(self, other: Ratio) -> Relative {
Relative { rel: other, abs: self }
}
}
impl Add<Length> for Ratio {
type Output = Relative;
fn add(self, other: Length) -> Relative {
other + self
}
}
impl Add<Length> for Relative {
type Output = Self; type Output = Self;
fn add(self, other: Length) -> Self { fn sub(self, other: Self) -> Self::Output {
Self { rel: self.rel, abs: self.abs + other } self + -other
} }
} }
impl Add<Relative> for Length { impl<T: Numeric> Mul<f64> for Relative<T> {
type Output = Relative;
fn add(self, other: Relative) -> Relative {
other + self
}
}
impl Add<Ratio> for Relative {
type Output = Self; type Output = Self;
fn add(self, other: Ratio) -> Self { fn mul(self, other: f64) -> Self::Output {
Self { rel: self.rel + other, abs: self.abs }
}
}
impl Add<Relative> 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<f64> for Relative {
type Output = Self;
fn mul(self, other: f64) -> Self {
Self { Self {
rel: self.rel * other, rel: self.rel * other,
abs: self.abs * other, abs: self.abs * other,
@ -145,18 +92,18 @@ impl Mul<f64> for Relative {
} }
} }
impl Mul<Relative> for f64 { impl<T: Numeric> Mul<Relative<T>> for f64 {
type Output = Relative; type Output = Relative<T>;
fn mul(self, other: Relative) -> Relative { fn mul(self, other: Relative<T>) -> Self::Output {
other * self other * self
} }
} }
impl Div<f64> for Relative { impl<T: Numeric> Div<f64> for Relative<T> {
type Output = Self; type Output = Self;
fn div(self, other: f64) -> Self { fn div(self, other: f64) -> Self::Output {
Self { Self {
rel: self.rel / other, rel: self.rel / other,
abs: self.abs / other, abs: self.abs / other,
@ -164,11 +111,50 @@ impl Div<f64> for Relative {
} }
} }
assign_impl!(Relative += Relative); impl<T: Numeric> AddAssign for Relative<T> {
assign_impl!(Relative += Length); fn add_assign(&mut self, other: Self) {
assign_impl!(Relative += Ratio); *self = *self + other;
assign_impl!(Relative -= Relative); }
assign_impl!(Relative -= Length); }
assign_impl!(Relative -= Ratio);
assign_impl!(Relative *= f64); impl<T: Numeric> SubAssign for Relative<T> {
assign_impl!(Relative /= f64); fn sub_assign(&mut self, other: Self) {
*self = *self - other;
}
}
impl<T: Numeric> MulAssign<f64> for Relative<T> {
fn mul_assign(&mut self, other: f64) {
*self = *self * other;
}
}
impl<T: Numeric> DivAssign<f64> for Relative<T> {
fn div_assign(&mut self, other: f64) {
*self = *self * other;
}
}
impl<T: Numeric> Add<T> for Ratio {
type Output = Relative<T>;
fn add(self, other: T) -> Self::Output {
Relative::from(self) + Relative::from(other)
}
}
impl<T: Numeric> Add<T> for Relative<T> {
type Output = Self;
fn add(self, other: T) -> Self::Output {
self + Relative::from(other)
}
}
impl<T: Numeric> Add<Ratio> for Relative<T> {
type Output = Self;
fn add(self, other: Ratio) -> Self::Output {
self + Relative::from(other)
}
}

View File

@ -6,6 +6,16 @@ use super::*;
#[derive(Default, Copy, Clone)] #[derive(Default, Copy, Clone)]
pub struct Scalar(pub f64); 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<f64> for Scalar { impl From<f64> for Scalar {
fn from(float: f64) -> Self { fn from(float: f64) -> Self {
Self(float) Self(float)

View File

@ -43,7 +43,7 @@ where
} }
} }
impl Sides<Relative> { impl Sides<Relative<Length>> {
/// Resolve the sides relative to the given `size`. /// Resolve the sides relative to the given `size`.
pub fn resolve(self, size: Size) -> Sides<Length> { pub fn resolve(self, size: Size) -> Sides<Length> {
Sides { Sides {

View File

@ -201,7 +201,7 @@ pub type Size = Spec<Length>;
impl Size { impl Size {
/// The zero value. /// The zero value.
pub fn zero() -> Self { pub const fn zero() -> Self {
Self { x: Length::zero(), y: Length::zero() } Self { x: Length::zero(), y: Length::zero() }
} }
@ -210,27 +210,22 @@ impl Size {
self.x.fits(other.x) && self.y.fits(other.y) 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. /// Convert to a point.
pub fn to_point(self) -> Point { pub fn to_point(self) -> Point {
Point::new(self.x, self.y) 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 { impl Neg for Size {
type Output = Self; type Output = Self;

View File

@ -24,18 +24,18 @@ impl Transform {
} }
} }
/// A translation transform. /// A translate transform.
pub const fn translation(tx: Length, ty: Length) -> Self { pub const fn translate(tx: Length, ty: Length) -> Self {
Self { tx, ty, ..Self::identity() } Self { tx, ty, ..Self::identity() }
} }
/// A scaling transform. /// A scale transform.
pub const fn scale(sx: Ratio, sy: Ratio) -> Self { pub const fn scale(sx: Ratio, sy: Ratio) -> Self {
Self { sx, sy, ..Self::identity() } Self { sx, sy, ..Self::identity() }
} }
/// A rotation transform. /// A rotate transform.
pub fn rotation(angle: Angle) -> Self { pub fn rotate(angle: Angle) -> Self {
let cos = Ratio::new(angle.cos()); let cos = Ratio::new(angle.cos());
let sin = Ratio::new(angle.sin()); let sin = Ratio::new(angle.sin());
Self { Self {

View File

@ -3,8 +3,10 @@ use crate::library::prelude::*;
/// Display a line without affecting the layout. /// Display a line without affecting the layout.
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct LineNode { pub struct LineNode {
origin: Spec<Relative>, /// Where the line starts.
delta: Spec<Relative>, origin: Spec<Relative<Length>>,
/// The offset from the `origin` where the line ends.
delta: Spec<Relative<Length>>,
} }
#[node] #[node]
@ -15,14 +17,15 @@ impl LineNode {
pub const THICKNESS: Length = Length::pt(1.0); pub const THICKNESS: Length = Length::pt(1.0);
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
let origin = args.named::<Spec<Relative>>("origin")?.unwrap_or_default(); let origin = args.named("origin")?.unwrap_or_default();
let delta = match args.named::<Spec<Relative>>("to")? { let delta = match args.named::<Spec<Relative<Length>>>("to")? {
Some(to) => to.zip(origin).map(|(to, from)| to - from), Some(to) => to.zip(origin).map(|(to, from)| to - from),
None => { None => {
let length = let length = args
args.named::<Relative>("length")?.unwrap_or(Length::cm(1.0).into()); .named::<Relative<Length>>("length")?
let angle = args.named::<Angle>("angle")?.unwrap_or_default(); .unwrap_or(Length::cm(1.0).into());
let angle = args.named::<Angle>("angle")?.unwrap_or_default();
let x = angle.cos() * length; let x = angle.cos() * length;
let y = angle.sin() * length; let y = angle.sin() * length;

View File

@ -28,7 +28,7 @@ impl<const S: ShapeKind> ShapeNode<S> {
/// The stroke's thickness. /// The stroke's thickness.
pub const THICKNESS: Length = Length::pt(1.0); pub const THICKNESS: Length = Length::pt(1.0);
/// How much to pad the shape's content. /// How much to pad the shape's content.
pub const PADDING: Relative = Relative::zero(); pub const PADDING: Relative<Length> = Relative::zero();
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
let size = match S { let size = match S {

View File

@ -29,11 +29,11 @@ impl<const T: TransformKind> TransformNode<T> {
MOVE => { MOVE => {
let tx = args.named("x")?.unwrap_or_default(); let tx = args.named("x")?.unwrap_or_default();
let ty = args.named("y")?.unwrap_or_default(); let ty = args.named("y")?.unwrap_or_default();
Transform::translation(tx, ty) Transform::translate(tx, ty)
} }
ROTATE => { ROTATE => {
let angle = args.named_or_find("angle")?.unwrap_or_default(); let angle = args.named_or_find("angle")?.unwrap_or_default();
Transform::rotation(angle) Transform::rotate(angle)
} }
SCALE | _ => { SCALE | _ => {
let all = args.find()?; let all = args.find()?;
@ -62,9 +62,9 @@ impl<const T: TransformKind> Layout for TransformNode<T> {
for frame in &mut frames { for frame in &mut frames {
let Spec { x, y } = origin.zip(frame.size).map(|(o, s)| o.resolve(s)); 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(self.transform)
.pre_concat(Transform::translation(-x, -y)); .pre_concat(Transform::translate(-x, -y));
Arc::make_mut(frame).transform(transform); Arc::make_mut(frame).transform(transform);
} }

View File

@ -14,7 +14,7 @@ pub struct ColumnsNode {
#[node] #[node]
impl ColumnsNode { impl ColumnsNode {
/// The size of the gutter space between each column. /// The size of the gutter space between each column.
pub const GUTTER: Relative = Ratio::new(0.04).into(); pub const GUTTER: Relative<Length> = Ratio::new(0.04).into();
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::block(Self { Ok(Content::block(Self {
@ -33,7 +33,7 @@ impl Layout for ColumnsNode {
) -> TypResult<Vec<Arc<Frame>>> { ) -> TypResult<Vec<Arc<Frame>>> {
// Separating the infinite space into infinite columns does not make // Separating the infinite space into infinite columns does not make
// much sense. // much sense.
if regions.first.x.is_infinite() { if !regions.first.x.is_finite() {
return self.child.layout(ctx, regions, styles); return self.child.layout(ctx, regions, styles);
} }

View File

@ -58,7 +58,7 @@ pub enum TrackSizing {
Auto, Auto,
/// A track size specified in absolute terms and relative to the parent's /// A track size specified in absolute terms and relative to the parent's
/// size. /// size.
Relative(Relative), Relative(Relative<Length>),
/// A track size specified as a fraction of the remaining free space in the /// A track size specified as a fraction of the remaining free space in the
/// parent. /// parent.
Fractional(Fraction), Fractional(Fraction),
@ -422,7 +422,7 @@ impl<'a> GridLayouter<'a> {
fn layout_relative_row( fn layout_relative_row(
&mut self, &mut self,
ctx: &mut Context, ctx: &mut Context,
v: Relative, v: Relative<Length>,
y: usize, y: usize,
) -> TypResult<()> { ) -> TypResult<()> {
let resolved = v.resolve(self.regions.base.y); let resolved = v.resolve(self.regions.base.y);

View File

@ -4,7 +4,7 @@ use crate::library::prelude::*;
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct PadNode { pub struct PadNode {
/// The amount of padding. /// The amount of padding.
pub padding: Sides<Relative>, pub padding: Sides<Relative<Length>>,
/// The child node whose sides to pad. /// The child node whose sides to pad.
pub child: LayoutNode, pub child: LayoutNode,
} }
@ -54,7 +54,7 @@ impl Layout for PadNode {
} }
/// Shrink a size by padding relative to the size itself. /// Shrink a size by padding relative to the size itself.
fn shrink(size: Size, padding: Sides<Relative>) -> Size { fn shrink(size: Size, padding: Sides<Relative<Length>>) -> Size {
size - padding.resolve(size).sum_by_axis() size - padding.resolve(size).sum_by_axis()
} }
@ -77,7 +77,7 @@ fn shrink(size: Size, padding: Sides<Relative>) -> Size {
/// <=> w - p.rel * w - p.abs = s /// <=> w - p.rel * w - p.abs = s
/// <=> (1 - p.rel) * w = s + p.abs /// <=> (1 - p.rel) * w = s + p.abs
/// <=> w = (s + p.abs) / (1 - p.rel) /// <=> w = (s + p.abs) / (1 - p.rel)
fn grow(size: Size, padding: Sides<Relative>) -> Size { fn grow(size: Size, padding: Sides<Relative<Length>>) -> Size {
size.zip(padding.sum_by_axis()) size.zip(padding.sum_by_axis())
.map(|(s, p)| (s + p.abs).safe_div(1.0 - p.rel.get())) .map(|(s, p)| (s + p.abs).safe_div(1.0 - p.rel.get()))
} }

View File

@ -16,13 +16,13 @@ impl PageNode {
/// Whether the page is flipped into landscape orientation. /// Whether the page is flipped into landscape orientation.
pub const FLIPPED: bool = false; pub const FLIPPED: bool = false;
/// The left margin. /// The left margin.
pub const LEFT: Smart<Relative> = Smart::Auto; pub const LEFT: Smart<Relative<Length>> = Smart::Auto;
/// The right margin. /// The right margin.
pub const RIGHT: Smart<Relative> = Smart::Auto; pub const RIGHT: Smart<Relative<Length>> = Smart::Auto;
/// The top margin. /// The top margin.
pub const TOP: Smart<Relative> = Smart::Auto; pub const TOP: Smart<Relative<Length>> = Smart::Auto;
/// The bottom margin. /// The bottom margin.
pub const BOTTOM: Smart<Relative> = Smart::Auto; pub const BOTTOM: Smart<Relative<Length>> = Smart::Auto;
/// The page's background color. /// The page's background color.
pub const FILL: Option<Paint> = None; pub const FILL: Option<Paint> = None;
/// How many columns the page has. /// How many columns the page has.
@ -85,7 +85,7 @@ impl PageNode {
} }
let mut min = width.min(height); let mut min = width.min(height);
if min.is_infinite() { if !min.is_finite() {
min = Paper::A4.width(); min = Paper::A4.width();
} }
@ -115,7 +115,7 @@ impl PageNode {
} }
// Layout the child. // 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, &regions, styles)?; let mut frames = child.layout(ctx, &regions, styles)?;
let header = styles.get(Self::HEADER); let header = styles.get(Self::HEADER);
@ -133,7 +133,7 @@ impl PageNode {
let pos = Point::new(padding.left, y); let pos = Point::new(padding.left, y);
let w = size.x - padding.left - padding.right; let w = size.x - padding.left - padding.right;
let area = Size::new(w, h); 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); let sub = Layout::layout(&content, ctx, &pod, styles)?.remove(0);
Arc::make_mut(frame).push_frame(pos, sub); Arc::make_mut(frame).push_frame(pos, sub);
} }

View File

@ -24,7 +24,7 @@ impl VNode {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Spacing { pub enum Spacing {
/// Spacing specified in absolute terms and relative to the parent's size. /// Spacing specified in absolute terms and relative to the parent's size.
Relative(Relative), Relative(Relative<Length>),
/// Spacing specified as a fraction of the remaining free space in the parent. /// Spacing specified as a fraction of the remaining free space in the parent.
Fractional(Fraction), Fractional(Fraction),
} }

View File

@ -167,16 +167,13 @@ castable! {
} }
castable! { castable! {
Spec<Relative>, Spec<Relative<Length>>,
Expected: "array of two relative lengths", Expected: "array of two relative lengths",
Value::Array(array) => { Value::Array(array) => {
match array.as_slice() { let mut iter = array.into_iter();
[a, b] => { match (iter.next(), iter.next(), iter.next()) {
let a = a.clone().cast::<Relative>()?; (Some(a), Some(b), None) => Spec::new(a.cast()?, b.cast()?),
let b = b.clone().cast::<Relative>()?; _ => Err("point array must contain exactly two entries")?,
Spec::new(a, b)
},
_ => return Err("point array must contain exactly two entries".to_string()),
} }
}, },
} }

View File

@ -34,11 +34,11 @@ impl<const L: ListKind> ListNode<L> {
#[property(referenced)] #[property(referenced)]
pub const LABEL: Label = Label::Default; pub const LABEL: Label = Label::Default;
/// The spacing between the list items of a non-wide list. /// The spacing between the list items of a non-wide list.
pub const SPACING: Relative = Relative::zero(); pub const SPACING: Relative<Length> = Relative::zero();
/// The indentation of each item's label. /// The indentation of each item's label.
pub const INDENT: Relative = Ratio::new(0.0).into(); pub const INDENT: Relative<Length> = Ratio::new(0.0).into();
/// The space between the label and the body of each item. /// 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<Length> = Ratio::new(0.5).into();
/// The extra padding above the list. /// The extra padding above the list.
pub const ABOVE: Length = Length::zero(); pub const ABOVE: Length = Length::zero();
/// The extra padding below the list. /// The extra padding below the list.

View File

@ -23,7 +23,7 @@ impl TableNode {
/// The stroke's thickness. /// The stroke's thickness.
pub const THICKNESS: Length = Length::pt(1.0); pub const THICKNESS: Length = Length::pt(1.0);
/// How much to pad the cells's content. /// How much to pad the cells's content.
pub const PADDING: Relative = Length::pt(5.0).into(); pub const PADDING: Relative<Length> = Length::pt(5.0).into();
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
let columns = args.named("columns")?.unwrap_or_default(); let columns = args.named("columns")?.unwrap_or_default();

View File

@ -26,13 +26,13 @@ impl<const L: DecoLine> DecoNode<L> {
/// Thickness of the line's strokes (dependent on scaled font size), read /// Thickness of the line's strokes (dependent on scaled font size), read
/// from the font tables if `None`. /// from the font tables if `None`.
#[property(shorthand)] #[property(shorthand)]
pub const THICKNESS: Option<Relative> = None; pub const THICKNESS: Option<Relative<Length>> = None;
/// Position of the line relative to the baseline (dependent on scaled font /// Position of the line relative to the baseline (dependent on scaled font
/// size), read from the font tables if `None`. /// size), read from the font tables if `None`.
pub const OFFSET: Option<Relative> = None; pub const OFFSET: Option<Relative<Length>> = None;
/// Amount that the line will be longer or shorter than its associated text /// Amount that the line will be longer or shorter than its associated text
/// (dependent on scaled font size). /// (dependent on scaled font size).
pub const EXTENT: Relative = Relative::zero(); pub const EXTENT: Relative<Length> = Relative::zero();
/// Whether the line skips sections in which it would collide /// Whether the line skips sections in which it would collide
/// with the glyphs. Does not apply to strikethrough. /// with the glyphs. Does not apply to strikethrough.
pub const EVADE: bool = true; pub const EVADE: bool = true;
@ -66,9 +66,9 @@ impl<const L: DecoLine> Show for DecoNode<L> {
pub struct Decoration { pub struct Decoration {
pub line: DecoLine, pub line: DecoLine,
pub stroke: Option<Paint>, pub stroke: Option<Paint>,
pub thickness: Option<Relative>, pub thickness: Option<Relative<Length>>,
pub offset: Option<Relative>, pub offset: Option<Relative<Length>>,
pub extent: Relative, pub extent: Relative<Length>,
pub evade: bool, pub evade: bool,
} }

View File

@ -188,7 +188,7 @@ castable! {
/// The size of text. /// The size of text.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct FontSize(pub Relative); pub struct FontSize(pub Relative<Length>);
impl Fold for FontSize { impl Fold for FontSize {
type Output = Length; type Output = Length;

View File

@ -42,11 +42,11 @@ impl ParNode {
/// will will be hyphenated if and only if justification is enabled. /// will will be hyphenated if and only if justification is enabled.
pub const HYPHENATE: Smart<bool> = Smart::Auto; pub const HYPHENATE: Smart<bool> = Smart::Auto;
/// The spacing between lines (dependent on scaled font size). /// The spacing between lines (dependent on scaled font size).
pub const LEADING: Relative = Ratio::new(0.65).into(); pub const LEADING: Relative<Length> = Ratio::new(0.65).into();
/// The extra spacing between paragraphs (dependent on scaled font size). /// The extra spacing between paragraphs (dependent on scaled font size).
pub const SPACING: Relative = Ratio::new(0.55).into(); pub const SPACING: Relative<Length> = Ratio::new(0.55).into();
/// The indent the first line of a consecutive paragraph should have. /// The indent the first line of a consecutive paragraph should have.
pub const INDENT: Relative = Relative::zero(); pub const INDENT: Relative<Length> = Relative::zero();
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
// The paragraph constructor is special: It doesn't create a paragraph // The paragraph constructor is special: It doesn't create a paragraph

View File

@ -8,7 +8,7 @@ pub fn rgb(_: &mut Context, args: &mut Args) -> TypResult<Value> {
if let Some(string) = args.find::<Spanned<EcoString>>()? { if let Some(string) = args.find::<Spanned<EcoString>>()? {
match RgbaColor::from_str(&string.v) { match RgbaColor::from_str(&string.v) {
Ok(color) => color, Ok(color) => color,
Err(_) => bail!(string.span, "invalid hex string"), Err(msg) => bail!(string.span, msg),
} }
} else { } else {
struct Component(u8); struct Component(u8);

View File

@ -20,7 +20,7 @@
#test(rgb(-30, 15, 50)) #test(rgb(-30, 15, 50))
--- ---
// Error: 6-11 invalid hex string // Error: 6-11 string contains non-hexadecimal letters
#rgb("lol") #rgb("lol")
--- ---