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 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<Option<Relative>>) -> Self {
pub fn sized(self, sizing: Spec<Option<Relative<Length>>>) -> 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<Relative>) -> Self {
pub fn padded(self, padding: Sides<Relative<Length>>) -> 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<Option<Relative>>,
sizing: Spec<Option<Relative<Length>>>,
/// The node to be sized.
child: LayoutNode,
}

View File

@ -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<Value> {
(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<Value> {
(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),

View File

@ -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<Length>),
/// 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<Length>: "relative length", Relative, Length(v) => v.into(), Ratio(v) => v.into() }
primitive! { Fraction: "fraction", Fraction }
primitive! { Color: "color", Color }
primitive! { EcoString: "string", Str }

View File

@ -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));

View File

@ -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<Length>),
}
/// Properties of a single font face.

View File

@ -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.

View File

@ -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 {

View File

@ -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()
}
}

View File

@ -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()))

View File

@ -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()))

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.
fn round_2(value: f64) -> f64 {
(value * 100.0).round() / 100.0

View File

@ -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<Self, Self::Err> {
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");
}
}

View File

@ -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()
}
}

View File

@ -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<T: Numeric>(self, whole: T) -> T {
let resolved = whole * self.get();
if resolved.is_finite() { resolved } else { T::zero() }
}
}
impl Debug for Ratio {

View File

@ -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<T: Numeric> {
/// 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<T: Numeric> Relative<T> {
/// 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<T: Numeric> Debug for Relative<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:?} + {:?}", self.rel, self.abs)
}
}
impl From<Length> for Relative {
fn from(abs: Length) -> Self {
impl<T: Numeric> From<T> for Relative<T> {
fn from(abs: T) -> Self {
Self { rel: Ratio::zero(), abs }
}
}
impl From<Ratio> for Relative {
impl<T: Numeric> From<Ratio> for Relative<T> {
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;
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;
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<Ratio> for Length {
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 {
impl<T: Numeric> Sub for Relative<T> {
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<Relative> for Length {
type Output = Relative;
fn add(self, other: Relative) -> Relative {
other + self
}
}
impl Add<Ratio> for Relative {
impl<T: Numeric> Mul<f64> for Relative<T> {
type Output = Self;
fn add(self, other: Ratio) -> Self {
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 {
fn mul(self, other: f64) -> Self::Output {
Self {
rel: self.rel * other,
abs: self.abs * other,
@ -145,18 +92,18 @@ impl Mul<f64> for Relative {
}
}
impl Mul<Relative> for f64 {
type Output = Relative;
impl<T: Numeric> Mul<Relative<T>> for f64 {
type Output = Relative<T>;
fn mul(self, other: Relative) -> Relative {
fn mul(self, other: Relative<T>) -> Self::Output {
other * self
}
}
impl Div<f64> for Relative {
impl<T: Numeric> Div<f64> for Relative<T> {
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<f64> 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<T: Numeric> AddAssign for Relative<T> {
fn add_assign(&mut self, other: Self) {
*self = *self + other;
}
}
impl<T: Numeric> SubAssign for Relative<T> {
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)]
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 {
fn from(float: f64) -> Self {
Self(float)

View File

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

View File

@ -201,7 +201,7 @@ pub type Size = Spec<Length>;
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;

View File

@ -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 {

View File

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

View File

@ -28,7 +28,7 @@ impl<const S: ShapeKind> ShapeNode<S> {
/// 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<Length> = Relative::zero();
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
let size = match S {

View File

@ -29,11 +29,11 @@ impl<const T: TransformKind> TransformNode<T> {
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<const T: TransformKind> Layout for TransformNode<T> {
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);
}

View File

@ -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<Length> = Ratio::new(0.04).into();
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::block(Self {
@ -33,7 +33,7 @@ impl Layout for ColumnsNode {
) -> TypResult<Vec<Arc<Frame>>> {
// 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);
}

View File

@ -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<Length>),
/// 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<Length>,
y: usize,
) -> TypResult<()> {
let resolved = v.resolve(self.regions.base.y);

View File

@ -4,7 +4,7 @@ use crate::library::prelude::*;
#[derive(Debug, Hash)]
pub struct PadNode {
/// The amount of padding.
pub padding: Sides<Relative>,
pub padding: Sides<Relative<Length>>,
/// 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<Relative>) -> Size {
fn shrink(size: Size, padding: Sides<Relative<Length>>) -> Size {
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
/// <=> (1 - p.rel) * w = s + p.abs
/// <=> 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())
.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.
pub const FLIPPED: bool = false;
/// The left margin.
pub const LEFT: Smart<Relative> = Smart::Auto;
pub const LEFT: Smart<Relative<Length>> = Smart::Auto;
/// The right margin.
pub const RIGHT: Smart<Relative> = Smart::Auto;
pub const RIGHT: Smart<Relative<Length>> = Smart::Auto;
/// The top margin.
pub const TOP: Smart<Relative> = Smart::Auto;
pub const TOP: Smart<Relative<Length>> = Smart::Auto;
/// The bottom margin.
pub const BOTTOM: Smart<Relative> = Smart::Auto;
pub const BOTTOM: Smart<Relative<Length>> = Smart::Auto;
/// The page's background color.
pub const FILL: Option<Paint> = 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, &regions, 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);
}

View File

@ -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<Length>),
/// Spacing specified as a fraction of the remaining free space in the parent.
Fractional(Fraction),
}

View File

@ -167,16 +167,13 @@ castable! {
}
castable! {
Spec<Relative>,
Spec<Relative<Length>>,
Expected: "array of two relative lengths",
Value::Array(array) => {
match array.as_slice() {
[a, b] => {
let a = a.clone().cast::<Relative>()?;
let b = b.clone().cast::<Relative>()?;
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")?,
}
},
}

View File

@ -34,11 +34,11 @@ impl<const L: ListKind> ListNode<L> {
#[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<Length> = Relative::zero();
/// 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.
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.
pub const ABOVE: Length = Length::zero();
/// The extra padding below the list.

View File

@ -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> = Length::pt(5.0).into();
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
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
/// from the font tables if `None`.
#[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
/// 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
/// (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
/// with the glyphs. Does not apply to strikethrough.
pub const EVADE: bool = true;
@ -66,9 +66,9 @@ impl<const L: DecoLine> Show for DecoNode<L> {
pub struct Decoration {
pub line: DecoLine,
pub stroke: Option<Paint>,
pub thickness: Option<Relative>,
pub offset: Option<Relative>,
pub extent: Relative,
pub thickness: Option<Relative<Length>>,
pub offset: Option<Relative<Length>>,
pub extent: Relative<Length>,
pub evade: bool,
}

View File

@ -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<Length>);
impl Fold for FontSize {
type Output = Length;

View File

@ -42,11 +42,11 @@ impl ParNode {
/// will will be hyphenated if and only if justification is enabled.
pub const HYPHENATE: Smart<bool> = Smart::Auto;
/// 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).
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.
pub const INDENT: Relative = Relative::zero();
pub const INDENT: Relative<Length> = Relative::zero();
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
// 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>>()? {
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);

View File

@ -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")
---