Refactor geom module

This commit is contained in:
Laurenz 2022-10-28 16:43:38 +02:00
parent 66030ae5d7
commit 95e9134a3c
60 changed files with 1410 additions and 1517 deletions

View File

@ -16,7 +16,7 @@ use self::outline::{Heading, HeadingNode};
use self::page::Page;
use crate::font::Font;
use crate::frame::{Frame, Lang};
use crate::geom::{Dir, Em, Length};
use crate::geom::{Abs, Dir, Em};
use crate::image::Image;
/// Export a collection of frames into a PDF file.
@ -182,13 +182,13 @@ where
}
}
/// Additional methods for [`Length`].
trait LengthExt {
/// Convert an em length to a number of points.
/// Additional methods for [`Abs`].
trait AbsExt {
/// Convert an to a number of points.
fn to_f32(self) -> f32;
}
impl LengthExt for Length {
impl AbsExt for Abs {
fn to_f32(self) -> f32 {
self.to_pt() as f32
}

View File

@ -1,7 +1,7 @@
use pdf_writer::{Finish, Ref, TextStr};
use super::{LengthExt, PdfContext, RefExt};
use crate::geom::{Length, Point};
use super::{AbsExt, PdfContext, RefExt};
use crate::geom::{Abs, Point};
use crate::util::EcoString;
/// A heading that can later be linked in the outline panel.
@ -77,7 +77,7 @@ pub fn write_outline_item(
outline.title(TextStr(&node.heading.content));
outline.dest_direct().page(node.heading.page).xyz(
node.heading.position.x.to_f32(),
(node.heading.position.y + Length::pt(3.0)).to_f32(),
(node.heading.position.y + Abs::pt(3.0)).to_f32(),
None,
);

View File

@ -3,12 +3,12 @@ use pdf_writer::writers::ColorSpace;
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str};
use super::{
deflate, EmExt, Heading, HeadingNode, LengthExt, PdfContext, RefExt, D65_GRAY, SRGB,
deflate, AbsExt, EmExt, Heading, HeadingNode, PdfContext, RefExt, D65_GRAY, SRGB,
};
use crate::font::Font;
use crate::frame::{Destination, Element, Frame, Group, Role, Text};
use crate::geom::{
self, Color, Em, Geometry, Length, Numeric, Paint, Point, Ratio, Shape, Size, Stroke,
self, Abs, Color, Em, Geometry, Numeric, Paint, Point, Ratio, Shape, Size, Stroke,
Transform,
};
use crate::image::Image;
@ -45,7 +45,7 @@ pub fn construct_page(ctx: &mut PdfContext, frame: &Frame) {
ky: Ratio::zero(),
kx: Ratio::zero(),
sy: Ratio::new(-1.0),
tx: Length::zero(),
tx: Abs::zero(),
ty: size.y,
});
@ -169,7 +169,7 @@ struct PageContext<'a> {
#[derive(Debug, Default, Clone)]
struct State {
transform: Transform,
font: Option<(Font, Length)>,
font: Option<(Font, Abs)>,
fill: Option<Paint>,
fill_space: Option<Name<'static>>,
stroke: Option<Stroke>,
@ -200,7 +200,7 @@ impl<'a> PageContext<'a> {
]);
}
fn set_font(&mut self, font: &Font, size: Length) {
fn set_font(&mut self, font: &Font, size: Abs) {
if self.state.font.as_ref().map(|(f, s)| (f, *s)) != Some((font, size)) {
self.parent.font_map.insert(font.clone());
let name = format_eco!("F{}", self.parent.font_map.map(font.clone()));
@ -402,6 +402,12 @@ fn write_shape(ctx: &mut PageContext, x: f32, y: f32, shape: &Shape) {
}
match shape.geometry {
Geometry::Line(target) => {
let dx = target.x.to_f32();
let dy = target.y.to_f32();
ctx.content.move_to(x, y);
ctx.content.line_to(x + dx, y + dy);
}
Geometry::Rect(size) => {
let w = size.x.to_f32();
let h = size.y.to_f32();
@ -409,16 +415,6 @@ fn write_shape(ctx: &mut PageContext, x: f32, y: f32, shape: &Shape) {
ctx.content.rect(x, y, w, h);
}
}
Geometry::Ellipse(size) => {
let approx = geom::Path::ellipse(size);
write_path(ctx, x, y, &approx);
}
Geometry::Line(target) => {
let dx = target.x.to_f32();
let dy = target.y.to_f32();
ctx.content.move_to(x, y);
ctx.content.line_to(x + dx, y + dy);
}
Geometry::Path(ref path) => {
write_path(ctx, x, y, path);
}
@ -469,10 +465,10 @@ fn write_image(ctx: &mut PageContext, x: f32, y: f32, image: &Image, size: Size)
/// Save a link for later writing in the annotations dictionary.
fn write_link(ctx: &mut PageContext, pos: Point, dest: &Destination, size: Size) {
let mut min_x = Length::inf();
let mut min_y = Length::inf();
let mut max_x = -Length::inf();
let mut max_y = -Length::inf();
let mut min_x = Abs::inf();
let mut min_y = Abs::inf();
let mut max_x = -Abs::inf();
let mut max_y = -Abs::inf();
// Compute the bounding box of the transformed link.
for point in [

View File

@ -10,7 +10,7 @@ use usvg::FitTo;
use crate::frame::{Element, Frame, Group, Text};
use crate::geom::{
self, Geometry, Length, Paint, PathElement, Shape, Size, Stroke, Transform,
self, Abs, Geometry, Paint, PathElement, Shape, Size, Stroke, Transform,
};
use crate::image::{DecodedImage, Image};
@ -286,18 +286,17 @@ fn render_shape(
shape: &Shape,
) -> Option<()> {
let path = match shape.geometry {
Geometry::Line(target) => {
let mut builder = sk::PathBuilder::new();
builder.line_to(target.x.to_f32(), target.y.to_f32());
builder.finish()?
}
Geometry::Rect(size) => {
let w = size.x.to_f32();
let h = size.y.to_f32();
let rect = sk::Rect::from_xywh(0.0, 0.0, w, h)?;
sk::PathBuilder::from_rect(rect)
}
Geometry::Ellipse(size) => convert_path(&geom::Path::ellipse(size))?,
Geometry::Line(target) => {
let mut builder = sk::PathBuilder::new();
builder.line_to(target.x.to_f32(), target.y.to_f32());
builder.finish()?
}
Geometry::Path(ref path) => convert_path(path)?,
};
@ -460,12 +459,12 @@ impl OutlineBuilder for WrappedPathBuilder {
}
/// Additional methods for [`Length`].
trait LengthExt {
/// Convert an em length to a number of points as f32.
trait AbsExt {
/// Convert to a number of points as f32.
fn to_f32(self) -> f32;
}
impl LengthExt for Length {
impl AbsExt for Abs {
fn to_f32(self) -> f32 {
self.to_pt() as f32
}

View File

@ -6,7 +6,7 @@ use std::sync::Arc;
use crate::font::Font;
use crate::geom::{
Align, Dir, Em, Length, Numeric, Paint, Point, Shape, Size, Spec, Transform,
Abs, Align, Axes, Dir, Em, Numeric, Paint, Point, Shape, Size, Transform,
};
use crate::image::Image;
use crate::model::{Dict, Value};
@ -19,7 +19,7 @@ pub struct Frame {
size: Size,
/// The baseline of the frame measured from the top. If this is `None`, the
/// frame's implicit baseline is at the bottom.
baseline: Option<Length>,
baseline: Option<Abs>,
/// The semantic role of the frame.
role: Option<Role>,
/// The elements composing this layout.
@ -58,22 +58,22 @@ impl Frame {
}
/// The width of the frame.
pub fn width(&self) -> Length {
pub fn width(&self) -> Abs {
self.size.x
}
/// The height of the frame.
pub fn height(&self) -> Length {
pub fn height(&self) -> Abs {
self.size.y
}
/// The baseline of the frame.
pub fn baseline(&self) -> Length {
pub fn baseline(&self) -> Abs {
self.baseline.unwrap_or(self.size.y)
}
/// Set the frame's baseline from the top.
pub fn set_baseline(&mut self, baseline: Length) {
pub fn set_baseline(&mut self, baseline: Abs) {
self.baseline = Some(baseline);
}
@ -221,7 +221,7 @@ impl Frame {
/// Resize the frame to a new size, distributing new space according to the
/// given alignments.
pub fn resize(&mut self, target: Size, aligns: Spec<Align>) {
pub fn resize(&mut self, target: Size, aligns: Axes<Align>) {
if self.size != target {
let offset = Point::new(
aligns.x.position(target.x - self.size.x),
@ -354,7 +354,7 @@ pub struct Text {
/// The font the glyphs are contained in.
pub font: Font,
/// The font size.
pub size: Length,
pub size: Abs,
/// Glyph color.
pub fill: Paint,
/// The natural language of the text.
@ -365,7 +365,7 @@ pub struct Text {
impl Text {
/// The width of the text run.
pub fn width(&self) -> Length {
pub fn width(&self) -> Abs {
self.glyphs.iter().map(|g| g.x_advance).sum::<Em>().at(self.size)
}
}

261
src/geom/abs.rs Normal file
View File

@ -0,0 +1,261 @@
use super::*;
/// An absolute length.
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Abs(Scalar);
impl Abs {
/// The zero length.
pub const fn zero() -> Self {
Self(Scalar(0.0))
}
/// The infinite length.
pub const fn inf() -> Self {
Self(Scalar(f64::INFINITY))
}
/// Create an absolute length from a number of raw units.
pub const fn raw(raw: f64) -> Self {
Self(Scalar(raw))
}
/// Create an absolute length from a value in a unit.
pub fn with_unit(val: f64, unit: AbsUnit) -> Self {
Self(Scalar(val * unit.raw_scale()))
}
/// Create an absolute length from a number of points.
pub fn pt(pt: f64) -> Self {
Self::with_unit(pt, AbsUnit::Pt)
}
/// Create an absolute length from a number of millimeters.
pub fn mm(mm: f64) -> Self {
Self::with_unit(mm, AbsUnit::Mm)
}
/// Create an absolute length from a number of centimeters.
pub fn cm(cm: f64) -> Self {
Self::with_unit(cm, AbsUnit::Cm)
}
/// Create an absolute length from a number of inches.
pub fn inches(inches: f64) -> Self {
Self::with_unit(inches, AbsUnit::In)
}
/// Get the value of this absolute length in raw units.
pub const fn to_raw(self) -> f64 {
(self.0).0
}
/// Get the value of this absolute length in a unit.
pub fn to_unit(self, unit: AbsUnit) -> f64 {
self.to_raw() / unit.raw_scale()
}
/// Convert this to a number of points.
pub fn to_pt(self) -> f64 {
self.to_unit(AbsUnit::Pt)
}
/// Convert this to a number of millimeters.
pub fn to_mm(self) -> f64 {
self.to_unit(AbsUnit::Mm)
}
/// Convert this to a number of centimeters.
pub fn to_cm(self) -> f64 {
self.to_unit(AbsUnit::Cm)
}
/// Convert this to a number of inches.
pub fn to_inches(self) -> f64 {
self.to_unit(AbsUnit::In)
}
/// The absolute value of this length.
pub fn abs(self) -> Self {
Self::raw(self.to_raw().abs())
}
/// The minimum of this and another absolute length.
pub fn min(self, other: Self) -> Self {
Self(self.0.min(other.0))
}
/// Set to the minimum of this and another absolute length.
pub fn set_min(&mut self, other: Self) {
*self = (*self).min(other);
}
/// The maximum of this and another absolute length.
pub fn max(self, other: Self) -> Self {
Self(self.0.max(other.0))
}
/// Set to the maximum of this and another absolute length.
pub fn set_max(&mut self, other: Self) {
*self = (*self).max(other);
}
/// Whether the other absolute length fits into this one (i.e. is smaller).
/// Allows for a bit of slack.
pub fn fits(self, other: Self) -> bool {
self.0 + 1e-6 >= other.0
}
/// Compares two absolute lengths for whether they are approximately equal.
pub fn approx_eq(self, other: Self) -> bool {
self == other || (self - other).to_raw().abs() < 1e-6
}
/// Perform a checked division by a number, returning zero if the result
/// is not finite.
pub fn safe_div(self, number: f64) -> Self {
let result = self.to_raw() / number;
if result.is_finite() {
Self::raw(result)
} else {
Self::zero()
}
}
}
impl Numeric for Abs {
fn zero() -> Self {
Self::zero()
}
fn is_finite(self) -> bool {
self.0.is_finite()
}
}
impl Debug for Abs {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}pt", round_2(self.to_pt()))
}
}
impl Neg for Abs {
type Output = Self;
fn neg(self) -> Self {
Self(-self.0)
}
}
impl Add for Abs {
type Output = Self;
fn add(self, other: Self) -> Self {
Self(self.0 + other.0)
}
}
sub_impl!(Abs - Abs -> Abs);
impl Mul<f64> for Abs {
type Output = Self;
fn mul(self, other: f64) -> Self {
Self(self.0 * other)
}
}
impl Mul<Abs> for f64 {
type Output = Abs;
fn mul(self, other: Abs) -> Abs {
other * self
}
}
impl Div<f64> for Abs {
type Output = Self;
fn div(self, other: f64) -> Self {
Self(self.0 / other)
}
}
impl Div for Abs {
type Output = f64;
fn div(self, other: Self) -> f64 {
self.to_raw() / other.to_raw()
}
}
assign_impl!(Abs += Abs);
assign_impl!(Abs -= Abs);
assign_impl!(Abs *= f64);
assign_impl!(Abs /= f64);
impl Rem for Abs {
type Output = Self;
fn rem(self, other: Self) -> Self::Output {
Self(self.0 % other.0)
}
}
impl Sum for Abs {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
Self(iter.map(|s| s.0).sum())
}
}
impl<'a> Sum<&'a Self> for Abs {
fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
Self(iter.map(|s| s.0).sum())
}
}
/// Different units of absolute measurement.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub enum AbsUnit {
/// Points.
Pt,
/// Millimeters.
Mm,
/// Centimeters.
Cm,
/// Inches.
In,
}
impl AbsUnit {
/// How many raw units correspond to a value of `1.0` in this unit.
fn raw_scale(self) -> f64 {
match self {
AbsUnit::Pt => 1.0,
AbsUnit::Mm => 2.83465,
AbsUnit::Cm => 28.3465,
AbsUnit::In => 72.0,
}
}
}
impl Debug for AbsUnit {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
AbsUnit::Mm => "mm",
AbsUnit::Pt => "pt",
AbsUnit::Cm => "cm",
AbsUnit::In => "in",
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_length_unit_conversion() {
assert!((Abs::mm(150.0).to_cm() - 15.0) < 1e-4);
}
}

View File

@ -19,16 +19,16 @@ pub enum Align {
impl Align {
/// Top-left alignment.
pub const LEFT_TOP: Spec<Self> = Spec { x: Align::Left, y: Align::Top };
pub const LEFT_TOP: Axes<Self> = Axes { x: Align::Left, y: Align::Top };
/// Center-horizon alignment.
pub const CENTER_HORIZON: Spec<Self> = Spec { x: Align::Center, y: Align::Horizon };
pub const CENTER_HORIZON: Axes<Self> = Axes { x: Align::Center, y: Align::Horizon };
/// The axis this alignment belongs to.
pub const fn axis(self) -> SpecAxis {
pub const fn axis(self) -> Axis {
match self {
Self::Left | Self::Center | Self::Right => SpecAxis::Horizontal,
Self::Top | Self::Horizon | Self::Bottom => SpecAxis::Vertical,
Self::Left | Self::Center | Self::Right => Axis::X,
Self::Top | Self::Horizon | Self::Bottom => Axis::Y,
}
}
@ -44,12 +44,13 @@ impl Align {
}
}
/// Returns the position of this alignment in the given length.
pub fn position(self, length: Length) -> Length {
/// Returns the position of this alignment in a container with the given
/// extentq.
pub fn position(self, extent: Abs) -> Abs {
match self {
Self::Left | Self::Top => Length::zero(),
Self::Center | Self::Horizon => length / 2.0,
Self::Right | Self::Bottom => length,
Self::Left | Self::Top => Abs::zero(),
Self::Center | Self::Horizon => extent / 2.0,
Self::Right | Self::Bottom => extent,
}
}
}

263
src/geom/axes.rs Normal file
View File

@ -0,0 +1,263 @@
use std::any::Any;
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not};
use super::*;
/// A container with a horizontal and vertical component.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Axes<T> {
/// The horizontal component.
pub x: T,
/// The vertical component.
pub y: T,
}
impl<T> Axes<T> {
/// Create a new instance from the two components.
pub const fn new(x: T, y: T) -> Self {
Self { x, y }
}
/// Create a new instance with two equal components.
pub fn splat(v: T) -> Self
where
T: Clone,
{
Self { x: v.clone(), y: v }
}
/// Map the individual fields with `f`.
pub fn map<F, U>(self, mut f: F) -> Axes<U>
where
F: FnMut(T) -> U,
{
Axes { x: f(self.x), y: f(self.y) }
}
/// Convert from `&Axes<T>` to `Axes<&T>`.
pub fn as_ref(&self) -> Axes<&T> {
Axes { x: &self.x, y: &self.y }
}
/// Convert from `&Axes<T>` to `Axes<&<T as Deref>::Target>`.
pub fn as_deref(&self) -> Axes<&T::Target>
where
T: Deref,
{
Axes { x: &self.x, y: &self.y }
}
/// Convert from `&mut Axes<T>` to `Axes<&mut T>`.
pub fn as_mut(&mut self) -> Axes<&mut T> {
Axes { x: &mut self.x, y: &mut self.y }
}
/// Zip two instances into an instance over a tuple.
pub fn zip<U>(self, other: Axes<U>) -> Axes<(T, U)> {
Axes {
x: (self.x, other.x),
y: (self.y, other.y),
}
}
/// Whether a condition is true for at least one of fields.
pub fn any<F>(self, mut f: F) -> bool
where
F: FnMut(&T) -> bool,
{
f(&self.x) || f(&self.y)
}
/// Whether a condition is true for both fields.
pub fn all<F>(self, mut f: F) -> bool
where
F: FnMut(&T) -> bool,
{
f(&self.x) && f(&self.y)
}
/// Filter the individual fields with a mask.
pub fn filter(self, mask: Axes<bool>) -> Axes<Option<T>> {
Axes {
x: if mask.x { Some(self.x) } else { None },
y: if mask.y { Some(self.y) } else { None },
}
}
}
impl<T: Default> Axes<T> {
/// Create a new instance with y set to its default value.
pub fn with_x(x: T) -> Self {
Self { x, y: T::default() }
}
/// Create a new instance with x set to its default value.
pub fn with_y(y: T) -> Self {
Self { x: T::default(), y }
}
}
impl<T: Ord> Axes<T> {
/// The component-wise minimum of this and another instance.
pub fn min(self, other: Self) -> Self {
Self {
x: self.x.min(other.x),
y: self.y.min(other.y),
}
}
/// The component-wise minimum of this and another instance.
pub fn max(self, other: Self) -> Self {
Self {
x: self.x.max(other.x),
y: self.y.max(other.y),
}
}
}
impl<T> Get<Axis> for Axes<T> {
type Component = T;
fn get(self, axis: Axis) -> T {
match axis {
Axis::X => self.x,
Axis::Y => self.y,
}
}
fn get_mut(&mut self, axis: Axis) -> &mut T {
match axis {
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
}
impl<T> Debug for Axes<T>
where
T: Debug + 'static,
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if let Axes { x: Some(x), y: Some(y) } =
self.as_ref().map(|v| (v as &dyn Any).downcast_ref::<Align>())
{
write!(f, "{:?}-{:?}", x, y)
} else if (&self.x as &dyn Any).is::<Abs>() {
write!(f, "Size({:?}, {:?})", self.x, self.y)
} else {
write!(f, "Axes({:?}, {:?})", self.x, self.y)
}
}
}
/// The two layouting axes.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub enum Axis {
/// The horizontal axis.
X,
/// The vertical axis.
Y,
}
impl Axis {
/// The direction with the given positivity for this axis.
pub fn dir(self, positive: bool) -> Dir {
match (self, positive) {
(Self::X, true) => Dir::LTR,
(Self::X, false) => Dir::RTL,
(Self::Y, true) => Dir::TTB,
(Self::Y, false) => Dir::BTT,
}
}
/// The other axis.
pub fn other(self) -> Self {
match self {
Self::X => Self::Y,
Self::Y => Self::X,
}
}
}
impl Debug for Axis {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::X => "horizontal",
Self::Y => "vertical",
})
}
}
impl<T> Axes<Option<T>> {
/// Unwrap the individual fields.
pub fn unwrap_or(self, other: Axes<T>) -> Axes<T> {
Axes {
x: self.x.unwrap_or(other.x),
y: self.y.unwrap_or(other.y),
}
}
}
impl Axes<bool> {
/// Select `t.x` if `self.x` is true and `f.x` otherwise and same for `y`.
pub fn select<T>(self, t: Axes<T>, f: Axes<T>) -> Axes<T> {
Axes {
x: if self.x { t.x } else { f.x },
y: if self.y { t.y } else { f.y },
}
}
}
impl Not for Axes<bool> {
type Output = Self;
fn not(self) -> Self::Output {
Self { x: !self.x, y: !self.y }
}
}
impl BitOr for Axes<bool> {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self { x: self.x | rhs.x, y: self.y | rhs.y }
}
}
impl BitOr<bool> for Axes<bool> {
type Output = Self;
fn bitor(self, rhs: bool) -> Self::Output {
Self { x: self.x | rhs, y: self.y | rhs }
}
}
impl BitAnd for Axes<bool> {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
Self { x: self.x & rhs.x, y: self.y & rhs.y }
}
}
impl BitAnd<bool> for Axes<bool> {
type Output = Self;
fn bitand(self, rhs: bool) -> Self::Output {
Self { x: self.x & rhs, y: self.y & rhs }
}
}
impl BitOrAssign for Axes<bool> {
fn bitor_assign(&mut self, rhs: Self) {
self.x |= rhs.x;
self.y |= rhs.y;
}
}
impl BitAndAssign for Axes<bool> {
fn bitand_assign(&mut self, rhs: Self) {
self.x &= rhs.x;
self.y &= rhs.y;
}
}

View File

@ -15,10 +15,10 @@ pub enum Dir {
impl Dir {
/// The specific axis this direction belongs to.
pub const fn axis(self) -> SpecAxis {
pub const fn axis(self) -> Axis {
match self {
Self::LTR | Self::RTL => SpecAxis::Horizontal,
Self::TTB | Self::BTT => SpecAxis::Vertical,
Self::LTR | Self::RTL => Axis::X,
Self::TTB | Self::BTT => Axis::Y,
}
}

26
src/geom/ellipse.rs Normal file
View File

@ -0,0 +1,26 @@
use super::*;
/// Produce a shape that approximates an axis-aligned ellipse.
pub fn ellipse(size: Size, fill: Option<Paint>, stroke: Option<Stroke>) -> Shape {
// https://stackoverflow.com/a/2007782
let z = Abs::zero();
let rx = size.x / 2.0;
let ry = size.y / 2.0;
let m = 0.551784;
let mx = m * rx;
let my = m * ry;
let point = |x, y| Point::new(x + rx, y + ry);
let mut path = Path::new();
path.move_to(point(-rx, z));
path.cubic_to(point(-rx, -my), point(-mx, -ry), point(z, -ry));
path.cubic_to(point(mx, -ry), point(rx, -my), point(rx, z));
path.cubic_to(point(rx, my), point(mx, ry), point(z, ry));
path.cubic_to(point(-mx, ry), point(-rx, my), point(-rx, z));
Shape {
geometry: Geometry::Path(path),
stroke,
fill,
}
}

View File

@ -7,7 +7,7 @@ use super::*;
pub struct Em(Scalar);
impl Em {
/// The zero length.
/// The zero em length.
pub const fn zero() -> Self {
Self(Scalar(0.0))
}
@ -28,7 +28,7 @@ impl Em {
}
/// Create an em length from a length at the given font size.
pub fn from_length(length: Length, font_size: Length) -> Self {
pub fn from_length(length: Abs, font_size: Abs) -> Self {
let result = length / font_size;
if result.is_finite() {
Self(Scalar(result))
@ -42,10 +42,10 @@ impl Em {
(self.0).0
}
/// Convert to a length at the given font size.
pub fn at(self, font_size: Length) -> Length {
/// Convert to an absolute length at the given font size.
pub fn at(self, font_size: Abs) -> Abs {
let resolved = font_size * self.get();
if resolved.is_finite() { resolved } else { Length::zero() }
if resolved.is_finite() { resolved } else { Abs::zero() }
}
}

View File

@ -2,9 +2,9 @@ use super::*;
/// A fraction of remaining space.
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Fraction(Scalar);
pub struct Fr(Scalar);
impl Fraction {
impl Fr {
/// Takes up zero space: `0fr`.
pub const fn zero() -> Self {
Self(Scalar(0.0))
@ -31,17 +31,17 @@ impl Fraction {
}
/// Determine this fraction's share in the remaining space.
pub fn share(self, total: Self, remaining: Length) -> Length {
pub fn share(self, total: Self, remaining: Abs) -> Abs {
let ratio = self / total;
if ratio.is_finite() && remaining.is_finite() {
ratio * remaining
} else {
Length::zero()
Abs::zero()
}
}
}
impl Numeric for Fraction {
impl Numeric for Fr {
fn zero() -> Self {
Self::zero()
}
@ -51,13 +51,13 @@ impl Numeric for Fraction {
}
}
impl Debug for Fraction {
impl Debug for Fr {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}fr", round_2(self.get()))
}
}
impl Neg for Fraction {
impl Neg for Fr {
type Output = Self;
fn neg(self) -> Self {
@ -65,7 +65,7 @@ impl Neg for Fraction {
}
}
impl Add for Fraction {
impl Add for Fr {
type Output = Self;
fn add(self, other: Self) -> Self {
@ -73,9 +73,9 @@ impl Add for Fraction {
}
}
sub_impl!(Fraction - Fraction -> Fraction);
sub_impl!(Fr - Fr -> Fr);
impl Mul<f64> for Fraction {
impl Mul<f64> for Fr {
type Output = Self;
fn mul(self, other: f64) -> Self {
@ -83,15 +83,15 @@ impl Mul<f64> for Fraction {
}
}
impl Mul<Fraction> for f64 {
type Output = Fraction;
impl Mul<Fr> for f64 {
type Output = Fr;
fn mul(self, other: Fraction) -> Fraction {
fn mul(self, other: Fr) -> Fr {
other * self
}
}
impl Div<f64> for Fraction {
impl Div<f64> for Fr {
type Output = Self;
fn div(self, other: f64) -> Self {
@ -99,7 +99,7 @@ impl Div<f64> for Fraction {
}
}
impl Div for Fraction {
impl Div for Fr {
type Output = f64;
fn div(self, other: Self) -> f64 {
@ -107,12 +107,12 @@ impl Div for Fraction {
}
}
assign_impl!(Fraction += Fraction);
assign_impl!(Fraction -= Fraction);
assign_impl!(Fraction *= f64);
assign_impl!(Fraction /= f64);
assign_impl!(Fr += Fr);
assign_impl!(Fr -= Fr);
assign_impl!(Fr *= f64);
assign_impl!(Fr /= f64);
impl Sum for Fraction {
impl Sum for Fr {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
Self(iter.map(|s| s.0).sum())
}

View File

@ -1,99 +0,0 @@
use super::*;
/// A container with a main and cross component.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Gen<T> {
/// The main component.
pub cross: T,
/// The cross component.
pub main: T,
}
impl<T> Gen<T> {
/// Create a new instance from the two components.
pub const fn new(cross: T, main: T) -> Self {
Self { cross, main }
}
/// Create a new instance with two equal components.
pub fn splat(value: T) -> Self
where
T: Clone,
{
Self { cross: value.clone(), main: value }
}
/// Maps the individual fields with `f`.
pub fn map<F, U>(self, mut f: F) -> Gen<U>
where
F: FnMut(T) -> U,
{
Gen { cross: f(self.cross), main: f(self.main) }
}
/// Convert to the specific representation, given the current main axis.
pub fn to_spec(self, main: SpecAxis) -> Spec<T> {
match main {
SpecAxis::Horizontal => Spec::new(self.main, self.cross),
SpecAxis::Vertical => Spec::new(self.cross, self.main),
}
}
}
impl Gen<Length> {
/// The zero value.
pub fn zero() -> Self {
Self {
cross: Length::zero(),
main: Length::zero(),
}
}
/// Convert to a point.
pub fn to_point(self, main: SpecAxis) -> Point {
self.to_spec(main).to_point()
}
}
impl<T> Get<GenAxis> for Gen<T> {
type Component = T;
fn get(self, axis: GenAxis) -> T {
match axis {
GenAxis::Cross => self.cross,
GenAxis::Main => self.main,
}
}
fn get_mut(&mut self, axis: GenAxis) -> &mut T {
match axis {
GenAxis::Cross => &mut self.cross,
GenAxis::Main => &mut self.main,
}
}
}
impl<T: Debug> Debug for Gen<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Gen({:?}, {:?})", self.cross, self.main)
}
}
/// Two generic axes of a container.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum GenAxis {
/// The minor / inline axis.
Cross,
/// The major / block axis.
Main,
}
impl GenAxis {
/// The other axis.
pub fn other(self) -> Self {
match self {
Self::Cross => Self::Main,
Self::Main => Self::Cross,
}
}
}

View File

@ -1,124 +1,41 @@
use super::*;
/// An absolute length.
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Length(Scalar);
/// A length, possibly expressed with contextual units.
///
/// Currently supports absolute and font-relative units, but support could quite
/// easily be extended to other units.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Length {
/// The absolute part.
pub abs: Abs,
/// The font-relative part.
pub em: Em,
}
impl Length {
/// The zero length.
pub const fn zero() -> Self {
Self(Scalar(0.0))
Self { abs: Abs::zero(), em: Em::zero() }
}
/// The infinite length.
pub const fn inf() -> Self {
Self(Scalar(f64::INFINITY))
}
/// Create a length from a number of raw units.
pub const fn raw(raw: f64) -> Self {
Self(Scalar(raw))
}
/// Create a length from a value in a unit.
pub fn with_unit(val: f64, unit: LengthUnit) -> Self {
Self(Scalar(val * unit.raw_scale()))
}
/// Create a length from a number of points.
pub fn pt(pt: f64) -> Self {
Self::with_unit(pt, LengthUnit::Pt)
}
/// Create a length from a number of millimeters.
pub fn mm(mm: f64) -> Self {
Self::with_unit(mm, LengthUnit::Mm)
}
/// Create a length from a number of centimeters.
pub fn cm(cm: f64) -> Self {
Self::with_unit(cm, LengthUnit::Cm)
}
/// Create a length from a number of inches.
pub fn inches(inches: f64) -> Self {
Self::with_unit(inches, LengthUnit::In)
}
/// Get the value of this length in raw units.
pub const fn to_raw(self) -> f64 {
(self.0).0
}
/// Get the value of this length in a unit.
pub fn to_unit(self, unit: LengthUnit) -> f64 {
self.to_raw() / unit.raw_scale()
}
/// Convert this to a number of points.
pub fn to_pt(self) -> f64 {
self.to_unit(LengthUnit::Pt)
}
/// Convert this to a number of millimeters.
pub fn to_mm(self) -> f64 {
self.to_unit(LengthUnit::Mm)
}
/// Convert this to a number of centimeters.
pub fn to_cm(self) -> f64 {
self.to_unit(LengthUnit::Cm)
}
/// Convert this to a number of inches.
pub fn to_inches(self) -> f64 {
self.to_unit(LengthUnit::In)
}
/// The absolute value of this length.
pub fn abs(self) -> Self {
Self::raw(self.to_raw().abs())
}
/// The minimum of this and another length.
pub fn min(self, other: Self) -> Self {
Self(self.0.min(other.0))
}
/// Set to the minimum of this and another length.
pub fn set_min(&mut self, other: Self) {
*self = (*self).min(other);
}
/// The maximum of this and another length.
pub fn max(self, other: Self) -> Self {
Self(self.0.max(other.0))
}
/// Set to the maximum of this and another length.
pub fn set_max(&mut self, other: Self) {
*self = (*self).max(other);
}
/// Whether the other length fits into this one (i.e. is smaller). Allows
/// for a bit of slack.
pub fn fits(self, other: Self) -> bool {
self.0 + 1e-6 >= other.0
}
/// Compares two lengths for whether they are approximately equal.
pub fn approx_eq(self, other: Self) -> bool {
self == other || (self - other).to_raw().abs() < 1e-6
}
/// Perform a checked division by a number, returning zero if the result
/// is not finite.
pub fn safe_div(self, number: f64) -> Self {
let result = self.to_raw() / number;
if result.is_finite() {
Self::raw(result)
/// Try to divide two lengths.
pub fn try_div(self, other: Self) -> Option<f64> {
if self.abs.is_zero() && other.abs.is_zero() {
Some(self.em / other.em)
} else if self.em.is_zero() && other.em.is_zero() {
Some(self.abs / other.abs)
} else {
Self::zero()
None
}
}
}
impl Debug for Length {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match (self.abs.is_zero(), self.em.is_zero()) {
(false, false) => write!(f, "{:?} + {:?}", self.abs, self.em),
(true, false) => self.em.fmt(f),
(_, true) => self.abs.fmt(f),
}
}
}
@ -129,29 +46,56 @@ impl Numeric for Length {
}
fn is_finite(self) -> bool {
self.0.is_finite()
self.abs.is_finite() && self.em.is_finite()
}
}
impl Debug for Length {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}pt", round_2(self.to_pt()))
impl PartialOrd for Length {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.em.is_zero() && other.em.is_zero() {
self.abs.partial_cmp(&other.abs)
} else if self.abs.is_zero() && other.abs.is_zero() {
self.em.partial_cmp(&other.em)
} else {
None
}
}
}
impl From<Abs> for Length {
fn from(abs: Abs) -> Self {
Self { abs, em: Em::zero() }
}
}
impl From<Em> for Length {
fn from(em: Em) -> Self {
Self { abs: Abs::zero(), em }
}
}
impl From<Abs> for Rel<Length> {
fn from(abs: Abs) -> Self {
Rel::from(Length::from(abs))
}
}
impl Neg for Length {
type Output = Self;
fn neg(self) -> Self {
Self(-self.0)
fn neg(self) -> Self::Output {
Self { abs: -self.abs, em: -self.em }
}
}
impl Add for Length {
type Output = Self;
fn add(self, other: Self) -> Self {
Self(self.0 + other.0)
fn add(self, rhs: Self) -> Self::Output {
Self {
abs: self.abs + rhs.abs,
em: self.em + rhs.em,
}
}
}
@ -160,32 +104,16 @@ sub_impl!(Length - Length -> Length);
impl Mul<f64> for Length {
type Output = Self;
fn mul(self, other: f64) -> Self {
Self(self.0 * other)
}
}
impl Mul<Length> for f64 {
type Output = Length;
fn mul(self, other: Length) -> Length {
other * self
fn mul(self, rhs: f64) -> Self::Output {
Self { abs: self.abs * rhs, em: self.em * rhs }
}
}
impl Div<f64> for Length {
type Output = Self;
fn div(self, other: f64) -> Self {
Self(self.0 / other)
}
}
impl Div for Length {
type Output = f64;
fn div(self, other: Self) -> f64 {
self.to_raw() / other.to_raw()
fn div(self, rhs: f64) -> Self::Output {
Self { abs: self.abs / rhs, em: self.em / rhs }
}
}
@ -193,69 +121,3 @@ assign_impl!(Length += Length);
assign_impl!(Length -= Length);
assign_impl!(Length *= f64);
assign_impl!(Length /= f64);
impl Rem for Length {
type Output = Self;
fn rem(self, other: Self) -> Self::Output {
Self(self.0 % other.0)
}
}
impl Sum for Length {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
Self(iter.map(|s| s.0).sum())
}
}
impl<'a> Sum<&'a Self> for Length {
fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
Self(iter.map(|s| s.0).sum())
}
}
/// Different units of length measurement.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub enum LengthUnit {
/// Points.
Pt,
/// Millimeters.
Mm,
/// Centimeters.
Cm,
/// Inches.
In,
}
impl LengthUnit {
/// How many raw units correspond to a value of `1.0` in this unit.
fn raw_scale(self) -> f64 {
match self {
LengthUnit::Pt => 1.0,
LengthUnit::Mm => 2.83465,
LengthUnit::Cm => 28.3465,
LengthUnit::In => 72.0,
}
}
}
impl Debug for LengthUnit {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
LengthUnit::Mm => "mm",
LengthUnit::Pt => "pt",
LengthUnit::Cm => "cm",
LengthUnit::In => "in",
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_length_unit_conversion() {
assert!((Length::mm(150.0).to_cm() - 15.0) < 1e-4);
}
}

View File

@ -2,42 +2,46 @@
#[macro_use]
mod macros;
mod abs;
mod align;
mod angle;
mod axes;
mod corners;
mod dir;
mod ellipse;
mod em;
mod fraction;
mod gen;
mod fr;
mod length;
mod paint;
mod path;
mod point;
mod ratio;
mod rect;
mod relative;
mod rel;
mod rounded;
mod scalar;
mod sides;
mod spec;
mod size;
mod transform;
pub use abs::*;
pub use align::*;
pub use angle::*;
pub use axes::*;
pub use corners::*;
pub use dir::*;
pub use ellipse::*;
pub use em::*;
pub use fraction::*;
pub use gen::*;
pub use fr::*;
pub use length::*;
pub use paint::*;
pub use path::*;
pub use point::*;
pub use ratio::*;
pub use rect::*;
pub use relative::*;
pub use rel::*;
pub use rounded::*;
pub use scalar::*;
pub use sides::*;
pub use spec::*;
pub use size::*;
pub use transform::*;
use std::cmp::Ordering;
@ -82,8 +86,6 @@ pub enum Geometry {
Line(Point),
/// A rectangle with its origin in the topleft corner.
Rect(Size),
/// A ellipse with its origin in the topleft corner.
Ellipse(Size),
/// A bezier path.
Path(Path),
}

View File

@ -394,14 +394,14 @@ pub struct Stroke {
/// The stroke's paint.
pub paint: Paint,
/// The stroke's thickness.
pub thickness: Length,
pub thickness: Abs,
}
impl Default for Stroke {
fn default() -> Self {
Self {
paint: Paint::Solid(Color::BLACK.into()),
thickness: Length::pt(1.0),
thickness: Abs::pt(1.0),
}
}
}

View File

@ -21,7 +21,7 @@ impl Path {
/// Create a path that describes a rectangle.
pub fn rect(size: Size) -> Self {
let z = Length::zero();
let z = Abs::zero();
let point = Point::new;
let mut path = Self::new();
path.move_to(point(z, z));
@ -32,25 +32,6 @@ impl Path {
path
}
/// Create a path that approximates an axis-aligned ellipse.
pub fn ellipse(size: Size) -> Self {
// https://stackoverflow.com/a/2007782
let z = Length::zero();
let rx = size.x / 2.0;
let ry = size.y / 2.0;
let m = 0.551784;
let mx = m * rx;
let my = m * ry;
let point = |x, y| Point::new(x + rx, y + ry);
let mut path = Self::new();
path.move_to(point(-rx, z));
path.cubic_to(point(-rx, -my), point(-mx, -ry), point(z, -ry));
path.cubic_to(point(mx, -ry), point(rx, -my), point(rx, z));
path.cubic_to(point(rx, my), point(mx, ry), point(z, ry));
path.cubic_to(point(-mx, ry), point(-rx, my), point(-rx, z));
path
}
/// Push a [`MoveTo`](PathElement::MoveTo) element.
pub fn move_to(&mut self, p: Point) {
self.0.push(PathElement::MoveTo(p));
@ -71,22 +52,3 @@ impl Path {
self.0.push(PathElement::ClosePath);
}
}
/// Get the control points for a bezier curve that describes a circular arc for
/// a start point, an end point and a center of the circle whose arc connects
/// the two.
pub fn bezier_arc(start: Point, center: Point, end: Point) -> [Point; 4] {
// https://stackoverflow.com/a/44829356/1567835
let a = start - center;
let b = end - center;
let q1 = a.x.to_raw() * a.x.to_raw() + a.y.to_raw() * a.y.to_raw();
let q2 = q1 + a.x.to_raw() * b.x.to_raw() + a.y.to_raw() * b.y.to_raw();
let k2 = (4.0 / 3.0) * ((2.0 * q1 * q2).sqrt() - q2)
/ (a.x.to_raw() * b.y.to_raw() - a.y.to_raw() * b.x.to_raw());
let control_1 = Point::new(center.x + a.x - k2 * a.y, center.y + a.y + k2 * a.x);
let control_2 = Point::new(center.x + b.x + k2 * b.y, center.y + b.y - k2 * b.x);
[start, control_1, control_2, end]
}

View File

@ -4,35 +4,35 @@ use super::*;
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Point {
/// The x coordinate.
pub x: Length,
pub x: Abs,
/// The y coordinate.
pub y: Length,
pub y: Abs,
}
impl Point {
/// The origin point.
pub const fn zero() -> Self {
Self { x: Length::zero(), y: Length::zero() }
Self { x: Abs::zero(), y: Abs::zero() }
}
/// Create a new point from x and y coordinates.
pub const fn new(x: Length, y: Length) -> Self {
pub const fn new(x: Abs, y: Abs) -> Self {
Self { x, y }
}
/// Create an instance with two equal components.
pub const fn splat(value: Length) -> Self {
pub const fn splat(value: Abs) -> Self {
Self { x: value, y: value }
}
/// Create a new point with y set to zero.
pub const fn with_x(x: Length) -> Self {
Self { x, y: Length::zero() }
pub const fn with_x(x: Abs) -> Self {
Self { x, y: Abs::zero() }
}
/// Create a new point with x set to zero.
pub const fn with_y(y: Length) -> Self {
Self { x: Length::zero(), y }
pub const fn with_y(y: Abs) -> Self {
Self { x: Abs::zero(), y }
}
/// Transform the point with the given transformation.
@ -54,20 +54,20 @@ impl Numeric for Point {
}
}
impl Get<SpecAxis> for Point {
type Component = Length;
impl Get<Axis> for Point {
type Component = Abs;
fn get(self, axis: SpecAxis) -> Length {
fn get(self, axis: Axis) -> Abs {
match axis {
SpecAxis::Horizontal => self.x,
SpecAxis::Vertical => self.y,
Axis::X => self.x,
Axis::Y => self.y,
}
}
fn get_mut(&mut self, axis: SpecAxis) -> &mut Length {
fn get_mut(&mut self, axis: Axis) -> &mut Abs {
match axis {
SpecAxis::Horizontal => &mut self.x,
SpecAxis::Vertical => &mut self.y,
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
}

View File

@ -1,184 +0,0 @@
use super::*;
use std::mem;
/// A rectangle with rounded corners.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct RoundedRect {
/// The size of the rectangle.
pub size: Size,
/// The radius at each corner.
pub radius: Corners<Length>,
}
impl RoundedRect {
/// Create a new rounded rectangle.
pub fn new(size: Size, radius: Corners<Length>) -> Self {
Self { size, radius }
}
/// Output all constituent shapes of the rectangle in order. The last one is
/// in the foreground. The function will output multiple items if the stroke
/// properties differ by side.
pub fn shapes(
self,
fill: Option<Paint>,
stroke: Sides<Option<Stroke>>,
) -> Vec<Shape> {
let mut res = vec![];
if fill.is_some() || (stroke.iter().any(Option::is_some) && stroke.is_uniform()) {
res.push(Shape {
geometry: self.fill_geometry(),
fill,
stroke: if stroke.is_uniform() { stroke.top } else { None },
});
}
if !stroke.is_uniform() {
for (path, stroke) in self.stroke_segments(stroke) {
if stroke.is_some() {
res.push(Shape {
geometry: Geometry::Path(path),
fill: None,
stroke,
});
}
}
}
res
}
/// Output the shape of the rectangle as a path or primitive rectangle,
/// depending on whether it is rounded.
fn fill_geometry(self) -> Geometry {
if self.radius.iter().copied().all(Length::is_zero) {
Geometry::Rect(self.size)
} else {
let mut paths = self.stroke_segments(Sides::splat(None));
assert_eq!(paths.len(), 1);
Geometry::Path(paths.pop().unwrap().0)
}
}
/// Output the minimum number of paths along the rectangles border.
fn stroke_segments(
self,
strokes: Sides<Option<Stroke>>,
) -> Vec<(Path, Option<Stroke>)> {
let mut res = vec![];
let mut connection = Connection::default();
let mut path = Path::new();
let mut always_continuous = true;
for side in [Side::Top, Side::Right, Side::Bottom, Side::Left] {
let continuous = strokes.get(side) == strokes.get(side.next_cw());
connection = connection.advance(continuous && side != Side::Left);
always_continuous &= continuous;
draw_side(
&mut path,
side,
self.size,
self.radius.get(side.start_corner()),
self.radius.get(side.end_corner()),
connection,
);
if !continuous {
res.push((mem::take(&mut path), strokes.get(side)));
}
}
if always_continuous {
path.close_path();
}
if !path.0.is_empty() {
res.push((path, strokes.left));
}
res
}
}
/// Draws one side of the rounded rectangle. Will always draw the left arc. The
/// right arc will be drawn halfway if and only if there is no connection.
fn draw_side(
path: &mut Path,
side: Side,
size: Size,
start_radius: Length,
end_radius: Length,
connection: Connection,
) {
let angle_left = Angle::deg(if connection.prev { 90.0 } else { 45.0 });
let angle_right = Angle::deg(if connection.next { 90.0 } else { 45.0 });
let length = size.get(side.axis());
// The arcs for a border of the rectangle along the x-axis, starting at (0,0).
let p1 = Point::with_x(start_radius);
let mut arc1 = bezier_arc(
p1 + Point::new(
-angle_left.sin() * start_radius,
(1.0 - angle_left.cos()) * start_radius,
),
Point::new(start_radius, start_radius),
p1,
);
let p2 = Point::with_x(length - end_radius);
let mut arc2 = bezier_arc(
p2,
Point::new(length - end_radius, end_radius),
p2 + Point::new(
angle_right.sin() * end_radius,
(1.0 - angle_right.cos()) * end_radius,
),
);
let transform = match side {
Side::Left => Transform::rotate(Angle::deg(-90.0))
.post_concat(Transform::translate(Length::zero(), size.y)),
Side::Bottom => Transform::rotate(Angle::deg(180.0))
.post_concat(Transform::translate(size.x, size.y)),
Side::Right => Transform::rotate(Angle::deg(90.0))
.post_concat(Transform::translate(size.x, Length::zero())),
_ => Transform::identity(),
};
arc1 = arc1.map(|x| x.transform(transform));
arc2 = arc2.map(|x| x.transform(transform));
if !connection.prev {
path.move_to(if start_radius.is_zero() { arc1[3] } else { arc1[0] });
}
if !start_radius.is_zero() {
path.cubic_to(arc1[1], arc1[2], arc1[3]);
}
path.line_to(arc2[0]);
if !connection.next && !end_radius.is_zero() {
path.cubic_to(arc2[1], arc2[2], arc2[3]);
}
}
/// Indicates which sides of the border strokes in a 2D polygon are connected to
/// their neighboring sides.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
struct Connection {
prev: bool,
next: bool,
}
impl Connection {
/// Advance to the next clockwise side of the polygon. The argument
/// indicates whether the border is connected on the right side of the next
/// edge.
pub fn advance(self, next: bool) -> Self {
Self { prev: self.next, next }
}
}

View File

@ -2,14 +2,14 @@ use super::*;
/// A value that is composed of a relative and an absolute part.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Relative<T: Numeric> {
pub struct Rel<T: Numeric> {
/// The relative part.
pub rel: Ratio,
/// The absolute part.
pub abs: T,
}
impl<T: Numeric> Relative<T> {
impl<T: Numeric> Rel<T> {
/// The zero relative.
pub fn zero() -> Self {
Self { rel: Ratio::zero(), abs: T::zero() }
@ -41,16 +41,29 @@ impl<T: Numeric> Relative<T> {
}
/// Map the absolute part with `f`.
pub fn map<F, U>(self, f: F) -> Relative<U>
pub fn map<F, U>(self, f: F) -> Rel<U>
where
F: FnOnce(T) -> U,
U: Numeric,
{
Relative { rel: self.rel, abs: f(self.abs) }
Rel { rel: self.rel, abs: f(self.abs) }
}
}
impl<T: Numeric> Debug for Relative<T> {
impl Rel<Length> {
/// Try to divide two relative lengths.
pub fn try_div(self, other: Self) -> Option<f64> {
if self.rel.is_zero() && other.rel.is_zero() {
self.abs.try_div(other.abs)
} else if self.abs.is_zero() && other.abs.is_zero() {
Some(self.rel / other.rel)
} else {
None
}
}
}
impl<T: Numeric> Debug for Rel<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match (self.rel.is_zero(), self.abs.is_zero()) {
(false, false) => write!(f, "{:?} + {:?}", self.rel, self.abs),
@ -60,19 +73,19 @@ impl<T: Numeric> Debug for Relative<T> {
}
}
impl<T: Numeric> From<T> for Relative<T> {
impl<T: Numeric> From<T> for Rel<T> {
fn from(abs: T) -> Self {
Self { rel: Ratio::zero(), abs }
}
}
impl<T: Numeric> From<Ratio> for Relative<T> {
impl<T: Numeric> From<Ratio> for Rel<T> {
fn from(rel: Ratio) -> Self {
Self { rel, abs: T::zero() }
}
}
impl<T: Numeric + PartialOrd> PartialOrd for Relative<T> {
impl<T: Numeric + PartialOrd> PartialOrd for Rel<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.rel.is_zero() && other.rel.is_zero() {
self.abs.partial_cmp(&other.abs)
@ -84,7 +97,7 @@ impl<T: Numeric + PartialOrd> PartialOrd for Relative<T> {
}
}
impl<T: Numeric> Neg for Relative<T> {
impl<T: Numeric> Neg for Rel<T> {
type Output = Self;
fn neg(self) -> Self {
@ -92,7 +105,7 @@ impl<T: Numeric> Neg for Relative<T> {
}
}
impl<T: Numeric> Add for Relative<T> {
impl<T: Numeric> Add for Rel<T> {
type Output = Self;
fn add(self, other: Self) -> Self::Output {
@ -103,7 +116,7 @@ impl<T: Numeric> Add for Relative<T> {
}
}
impl<T: Numeric> Sub for Relative<T> {
impl<T: Numeric> Sub for Rel<T> {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
@ -111,7 +124,7 @@ impl<T: Numeric> Sub for Relative<T> {
}
}
impl<T: Numeric> Mul<f64> for Relative<T> {
impl<T: Numeric> Mul<f64> for Rel<T> {
type Output = Self;
fn mul(self, other: f64) -> Self::Output {
@ -122,15 +135,15 @@ impl<T: Numeric> Mul<f64> for Relative<T> {
}
}
impl<T: Numeric> Mul<Relative<T>> for f64 {
type Output = Relative<T>;
impl<T: Numeric> Mul<Rel<T>> for f64 {
type Output = Rel<T>;
fn mul(self, other: Relative<T>) -> Self::Output {
fn mul(self, other: Rel<T>) -> Self::Output {
other * self
}
}
impl<T: Numeric> Div<f64> for Relative<T> {
impl<T: Numeric> Div<f64> for Rel<T> {
type Output = Self;
fn div(self, other: f64) -> Self::Output {
@ -141,28 +154,28 @@ impl<T: Numeric> Div<f64> for Relative<T> {
}
}
impl<T: Numeric + AddAssign> AddAssign for Relative<T> {
impl<T: Numeric + AddAssign> AddAssign for Rel<T> {
fn add_assign(&mut self, other: Self) {
self.rel += other.rel;
self.abs += other.abs;
}
}
impl<T: Numeric + SubAssign> SubAssign for Relative<T> {
impl<T: Numeric + SubAssign> SubAssign for Rel<T> {
fn sub_assign(&mut self, other: Self) {
self.rel -= other.rel;
self.abs -= other.abs;
}
}
impl<T: Numeric + MulAssign<f64>> MulAssign<f64> for Relative<T> {
impl<T: Numeric + MulAssign<f64>> MulAssign<f64> for Rel<T> {
fn mul_assign(&mut self, other: f64) {
self.rel *= other;
self.abs *= other;
}
}
impl<T: Numeric + DivAssign<f64>> DivAssign<f64> for Relative<T> {
impl<T: Numeric + DivAssign<f64>> DivAssign<f64> for Rel<T> {
fn div_assign(&mut self, other: f64) {
self.rel /= other;
self.abs /= other;
@ -170,25 +183,25 @@ impl<T: Numeric + DivAssign<f64>> DivAssign<f64> for Relative<T> {
}
impl<T: Numeric> Add<T> for Ratio {
type Output = Relative<T>;
type Output = Rel<T>;
fn add(self, other: T) -> Self::Output {
Relative::from(self) + Relative::from(other)
Rel::from(self) + Rel::from(other)
}
}
impl<T: Numeric> Add<T> for Relative<T> {
impl<T: Numeric> Add<T> for Rel<T> {
type Output = Self;
fn add(self, other: T) -> Self::Output {
self + Relative::from(other)
self + Rel::from(other)
}
}
impl<T: Numeric> Add<Ratio> for Relative<T> {
impl<T: Numeric> Add<Ratio> for Rel<T> {
type Output = Self;
fn add(self, other: Ratio) -> Self::Output {
self + Relative::from(other)
self + Rel::from(other)
}
}

187
src/geom/rounded.rs Normal file
View File

@ -0,0 +1,187 @@
use super::*;
use std::mem;
/// Produce shapes that together make up a rounded rectangle.
pub fn rounded_rect(
size: Size,
radius: Corners<Abs>,
fill: Option<Paint>,
stroke: Sides<Option<Stroke>>,
) -> Vec<Shape> {
let mut res = vec![];
if fill.is_some() || (stroke.iter().any(Option::is_some) && stroke.is_uniform()) {
res.push(Shape {
geometry: fill_geometry(size, radius),
fill,
stroke: if stroke.is_uniform() { stroke.top } else { None },
});
}
if !stroke.is_uniform() {
for (path, stroke) in stroke_segments(size, radius, stroke) {
if stroke.is_some() {
res.push(Shape {
geometry: Geometry::Path(path),
fill: None,
stroke,
});
}
}
}
res
}
/// Output the shape of the rectangle as a path or primitive rectangle,
/// depending on whether it is rounded.
fn fill_geometry(size: Size, radius: Corners<Abs>) -> Geometry {
if radius.iter().copied().all(Abs::is_zero) {
Geometry::Rect(size)
} else {
let mut paths = stroke_segments(size, radius, Sides::splat(None));
assert_eq!(paths.len(), 1);
Geometry::Path(paths.pop().unwrap().0)
}
}
/// Output the minimum number of paths along the rectangles border.
fn stroke_segments(
size: Size,
radius: Corners<Abs>,
stroke: Sides<Option<Stroke>>,
) -> Vec<(Path, Option<Stroke>)> {
let mut res = vec![];
let mut connection = Connection::default();
let mut path = Path::new();
let mut always_continuous = true;
for side in [Side::Top, Side::Right, Side::Bottom, Side::Left] {
let continuous = stroke.get(side) == stroke.get(side.next_cw());
connection = connection.advance(continuous && side != Side::Left);
always_continuous &= continuous;
draw_side(
&mut path,
side,
size,
radius.get(side.start_corner()),
radius.get(side.end_corner()),
connection,
);
if !continuous {
res.push((mem::take(&mut path), stroke.get(side)));
}
}
if always_continuous {
path.close_path();
}
if !path.0.is_empty() {
res.push((path, stroke.left));
}
res
}
/// Draws one side of the rounded rectangle. Will always draw the left arc. The
/// right arc will be drawn halfway if and only if there is no connection.
fn draw_side(
path: &mut Path,
side: Side,
size: Size,
start_radius: Abs,
end_radius: Abs,
connection: Connection,
) {
let angle_left = Angle::deg(if connection.prev { 90.0 } else { 45.0 });
let angle_right = Angle::deg(if connection.next { 90.0 } else { 45.0 });
let length = size.get(side.axis());
// The arcs for a border of the rectangle along the x-axis, starting at (0,0).
let p1 = Point::with_x(start_radius);
let mut arc1 = bezier_arc(
p1 + Point::new(
-angle_left.sin() * start_radius,
(1.0 - angle_left.cos()) * start_radius,
),
Point::new(start_radius, start_radius),
p1,
);
let p2 = Point::with_x(length - end_radius);
let mut arc2 = bezier_arc(
p2,
Point::new(length - end_radius, end_radius),
p2 + Point::new(
angle_right.sin() * end_radius,
(1.0 - angle_right.cos()) * end_radius,
),
);
let transform = match side {
Side::Left => Transform::rotate(Angle::deg(-90.0))
.post_concat(Transform::translate(Abs::zero(), size.y)),
Side::Bottom => Transform::rotate(Angle::deg(180.0))
.post_concat(Transform::translate(size.x, size.y)),
Side::Right => Transform::rotate(Angle::deg(90.0))
.post_concat(Transform::translate(size.x, Abs::zero())),
_ => Transform::identity(),
};
arc1 = arc1.map(|x| x.transform(transform));
arc2 = arc2.map(|x| x.transform(transform));
if !connection.prev {
path.move_to(if start_radius.is_zero() { arc1[3] } else { arc1[0] });
}
if !start_radius.is_zero() {
path.cubic_to(arc1[1], arc1[2], arc1[3]);
}
path.line_to(arc2[0]);
if !connection.next && !end_radius.is_zero() {
path.cubic_to(arc2[1], arc2[2], arc2[3]);
}
}
/// Get the control points for a bezier curve that describes a circular arc for
/// a start point, an end point and a center of the circle whose arc connects
/// the two.
fn bezier_arc(start: Point, center: Point, end: Point) -> [Point; 4] {
// https://stackoverflow.com/a/44829356/1567835
let a = start - center;
let b = end - center;
let q1 = a.x.to_raw() * a.x.to_raw() + a.y.to_raw() * a.y.to_raw();
let q2 = q1 + a.x.to_raw() * b.x.to_raw() + a.y.to_raw() * b.y.to_raw();
let k2 = (4.0 / 3.0) * ((2.0 * q1 * q2).sqrt() - q2)
/ (a.x.to_raw() * b.y.to_raw() - a.y.to_raw() * b.x.to_raw());
let control_1 = Point::new(center.x + a.x - k2 * a.y, center.y + a.y + k2 * a.x);
let control_2 = Point::new(center.x + b.x + k2 * b.y, center.y + b.y - k2 * b.x);
[start, control_1, control_2, end]
}
/// Indicates which sides of the border strokes in a 2D polygon are connected to
/// their neighboring sides.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
struct Connection {
prev: bool,
next: bool,
}
impl Connection {
/// Advance to the next clockwise side of the polygon. The argument
/// indicates whether the border is connected on the right side of the next
/// edge.
pub fn advance(self, next: bool) -> Self {
Self { prev: self.next, next }
}
}

View File

@ -74,14 +74,14 @@ impl<T> Sides<T> {
impl<T: Add> Sides<T> {
/// Sums up `left` and `right` into `x`, and `top` and `bottom` into `y`.
pub fn sum_by_axis(self) -> Spec<T::Output> {
Spec::new(self.left + self.right, self.top + self.bottom)
pub fn sum_by_axis(self) -> Axes<T::Output> {
Axes::new(self.left + self.right, self.top + self.bottom)
}
}
impl Sides<Relative<Length>> {
impl Sides<Rel<Abs>> {
/// Evaluate the sides relative to the given `size`.
pub fn relative_to(self, size: Size) -> Sides<Length> {
pub fn relative_to(self, size: Size) -> Sides<Abs> {
Sides {
left: self.left.relative_to(size.x),
top: self.top.relative_to(size.y),
@ -173,10 +173,10 @@ impl Side {
}
/// Return the corresponding axis.
pub fn axis(self) -> SpecAxis {
pub fn axis(self) -> Axis {
match self {
Self::Left | Self::Right => SpecAxis::Vertical,
Self::Top | Self::Bottom => SpecAxis::Horizontal,
Self::Left | Self::Right => Axis::Y,
Self::Top | Self::Bottom => Axis::X,
}
}
}

78
src/geom/size.rs Normal file
View File

@ -0,0 +1,78 @@
use super::*;
/// A size in 2D.
pub type Size = Axes<Abs>;
impl Size {
/// The zero value.
pub const fn zero() -> Self {
Self { x: Abs::zero(), y: Abs::zero() }
}
/// Whether the other size fits into this one (smaller width and height).
pub fn fits(self, other: Self) -> bool {
self.x.fits(other.x) && self.y.fits(other.y)
}
/// Convert to a point.
pub fn to_point(self) -> Point {
Point::new(self.x, self.y)
}
}
impl Numeric for Size {
fn zero() -> Self {
Self::zero()
}
fn is_finite(self) -> bool {
self.x.is_finite() && self.y.is_finite()
}
}
impl Neg for Size {
type Output = Self;
fn neg(self) -> Self {
Self { x: -self.x, y: -self.y }
}
}
impl Add for Size {
type Output = Self;
fn add(self, other: Self) -> Self {
Self { x: self.x + other.x, y: self.y + other.y }
}
}
sub_impl!(Size - Size -> Size);
impl Mul<f64> for Size {
type Output = Self;
fn mul(self, other: f64) -> Self {
Self { x: self.x * other, y: self.y * other }
}
}
impl Mul<Size> for f64 {
type Output = Size;
fn mul(self, other: Size) -> Size {
other * self
}
}
impl Div<f64> for Size {
type Output = Self;
fn div(self, other: f64) -> Self {
Self { x: self.x / other, y: self.y / other }
}
}
assign_impl!(Size -= Size);
assign_impl!(Size += Size);
assign_impl!(Size *= f64);
assign_impl!(Size /= f64);

View File

@ -1,358 +0,0 @@
use std::any::Any;
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not};
use super::*;
/// A container with a horizontal and vertical component.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Spec<T> {
/// The horizontal component.
pub x: T,
/// The vertical component.
pub y: T,
}
impl<T> Spec<T> {
/// Create a new instance from the two components.
pub const fn new(x: T, y: T) -> Self {
Self { x, y }
}
/// Create a new instance with two equal components.
pub fn splat(v: T) -> Self
where
T: Clone,
{
Self { x: v.clone(), y: v }
}
/// Map the individual fields with `f`.
pub fn map<F, U>(self, mut f: F) -> Spec<U>
where
F: FnMut(T) -> U,
{
Spec { x: f(self.x), y: f(self.y) }
}
/// Convert from `&Spec<T>` to `Spec<&T>`.
pub fn as_ref(&self) -> Spec<&T> {
Spec { x: &self.x, y: &self.y }
}
/// Convert from `&Spec<T>` to `Spec<&<T as Deref>::Target>`.
pub fn as_deref(&self) -> Spec<&T::Target>
where
T: Deref,
{
Spec { x: &self.x, y: &self.y }
}
/// Convert from `&mut Spec<T>` to `Spec<&mut T>`.
pub fn as_mut(&mut self) -> Spec<&mut T> {
Spec { x: &mut self.x, y: &mut self.y }
}
/// Zip two instances into an instance over a tuple.
pub fn zip<U>(self, other: Spec<U>) -> Spec<(T, U)> {
Spec {
x: (self.x, other.x),
y: (self.y, other.y),
}
}
/// Whether a condition is true for at least one of fields.
pub fn any<F>(self, mut f: F) -> bool
where
F: FnMut(&T) -> bool,
{
f(&self.x) || f(&self.y)
}
/// Whether a condition is true for both fields.
pub fn all<F>(self, mut f: F) -> bool
where
F: FnMut(&T) -> bool,
{
f(&self.x) && f(&self.y)
}
/// Filter the individual fields with a mask.
pub fn filter(self, mask: Spec<bool>) -> Spec<Option<T>> {
Spec {
x: if mask.x { Some(self.x) } else { None },
y: if mask.y { Some(self.y) } else { None },
}
}
/// Convert to the generic representation.
pub fn to_gen(self, main: SpecAxis) -> Gen<T> {
match main {
SpecAxis::Horizontal => Gen::new(self.y, self.x),
SpecAxis::Vertical => Gen::new(self.x, self.y),
}
}
}
impl<T: Default> Spec<T> {
/// Create a new instance with y set to its default value.
pub fn with_x(x: T) -> Self {
Self { x, y: T::default() }
}
/// Create a new instance with x set to its default value.
pub fn with_y(y: T) -> Self {
Self { x: T::default(), y }
}
}
impl<T: Ord> Spec<T> {
/// The component-wise minimum of this and another instance.
pub fn min(self, other: Self) -> Self {
Self {
x: self.x.min(other.x),
y: self.y.min(other.y),
}
}
/// The component-wise minimum of this and another instance.
pub fn max(self, other: Self) -> Self {
Self {
x: self.x.max(other.x),
y: self.y.max(other.y),
}
}
}
impl<T> Get<SpecAxis> for Spec<T> {
type Component = T;
fn get(self, axis: SpecAxis) -> T {
match axis {
SpecAxis::Horizontal => self.x,
SpecAxis::Vertical => self.y,
}
}
fn get_mut(&mut self, axis: SpecAxis) -> &mut T {
match axis {
SpecAxis::Horizontal => &mut self.x,
SpecAxis::Vertical => &mut self.y,
}
}
}
impl<T> Debug for Spec<T>
where
T: Debug + 'static,
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if let Spec { x: Some(x), y: Some(y) } =
self.as_ref().map(|v| (v as &dyn Any).downcast_ref::<Align>())
{
write!(f, "{:?}-{:?}", x, y)
} else if (&self.x as &dyn Any).is::<Length>() {
write!(f, "Size({:?}, {:?})", self.x, self.y)
} else {
write!(f, "Spec({:?}, {:?})", self.x, self.y)
}
}
}
/// The two specific layouting axes.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub enum SpecAxis {
/// The horizontal layouting axis.
Horizontal,
/// The vertical layouting axis.
Vertical,
}
impl SpecAxis {
/// The direction with the given positivity for this axis.
pub fn dir(self, positive: bool) -> Dir {
match (self, positive) {
(Self::Horizontal, true) => Dir::LTR,
(Self::Horizontal, false) => Dir::RTL,
(Self::Vertical, true) => Dir::TTB,
(Self::Vertical, false) => Dir::BTT,
}
}
/// The other axis.
pub fn other(self) -> Self {
match self {
Self::Horizontal => Self::Vertical,
Self::Vertical => Self::Horizontal,
}
}
}
impl Debug for SpecAxis {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::Horizontal => "horizontal",
Self::Vertical => "vertical",
})
}
}
/// A size in 2D.
pub type Size = Spec<Length>;
impl Size {
/// The zero value.
pub const fn zero() -> Self {
Self { x: Length::zero(), y: Length::zero() }
}
/// Whether the other size fits into this one (smaller width and height).
pub fn fits(self, other: Self) -> bool {
self.x.fits(other.x) && self.y.fits(other.y)
}
/// Convert to a point.
pub fn to_point(self) -> Point {
Point::new(self.x, self.y)
}
}
impl Numeric for Size {
fn zero() -> Self {
Self::zero()
}
fn is_finite(self) -> bool {
self.x.is_finite() && self.y.is_finite()
}
}
impl Neg for Size {
type Output = Self;
fn neg(self) -> Self {
Self { x: -self.x, y: -self.y }
}
}
impl Add for Size {
type Output = Self;
fn add(self, other: Self) -> Self {
Self { x: self.x + other.x, y: self.y + other.y }
}
}
sub_impl!(Size - Size -> Size);
impl Mul<f64> for Size {
type Output = Self;
fn mul(self, other: f64) -> Self {
Self { x: self.x * other, y: self.y * other }
}
}
impl Mul<Size> for f64 {
type Output = Size;
fn mul(self, other: Size) -> Size {
other * self
}
}
impl Div<f64> for Size {
type Output = Self;
fn div(self, other: f64) -> Self {
Self { x: self.x / other, y: self.y / other }
}
}
assign_impl!(Size -= Size);
assign_impl!(Size += Size);
assign_impl!(Size *= f64);
assign_impl!(Size /= f64);
impl<T> Spec<Option<T>> {
/// Whether the individual fields are some.
pub fn map_is_some(&self) -> Spec<bool> {
self.as_ref().map(Option::is_some)
}
/// Whether the individual fields are none.
pub fn map_is_none(&self) -> Spec<bool> {
self.as_ref().map(Option::is_none)
}
/// Unwrap the individual fields.
pub fn unwrap_or(self, other: Spec<T>) -> Spec<T> {
Spec {
x: self.x.unwrap_or(other.x),
y: self.y.unwrap_or(other.y),
}
}
}
impl Spec<bool> {
/// Select `t.x` if `self.x` is true and `f.x` otherwise and same for `y`.
pub fn select<T>(self, t: Spec<T>, f: Spec<T>) -> Spec<T> {
Spec {
x: if self.x { t.x } else { f.x },
y: if self.y { t.y } else { f.y },
}
}
}
impl Not for Spec<bool> {
type Output = Self;
fn not(self) -> Self::Output {
Self { x: !self.x, y: !self.y }
}
}
impl BitOr for Spec<bool> {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self { x: self.x | rhs.x, y: self.y | rhs.y }
}
}
impl BitOr<bool> for Spec<bool> {
type Output = Self;
fn bitor(self, rhs: bool) -> Self::Output {
Self { x: self.x | rhs, y: self.y | rhs }
}
}
impl BitAnd for Spec<bool> {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
Self { x: self.x & rhs.x, y: self.y & rhs.y }
}
}
impl BitAnd<bool> for Spec<bool> {
type Output = Self;
fn bitand(self, rhs: bool) -> Self::Output {
Self { x: self.x & rhs, y: self.y & rhs }
}
}
impl BitOrAssign for Spec<bool> {
fn bitor_assign(&mut self, rhs: Self) {
self.x |= rhs.x;
self.y |= rhs.y;
}
}
impl BitAndAssign for Spec<bool> {
fn bitand_assign(&mut self, rhs: Self) {
self.x &= rhs.x;
self.y &= rhs.y;
}
}

View File

@ -7,8 +7,8 @@ pub struct Transform {
pub ky: Ratio,
pub kx: Ratio,
pub sy: Ratio,
pub tx: Length,
pub ty: Length,
pub tx: Abs,
pub ty: Abs,
}
impl Transform {
@ -19,13 +19,13 @@ impl Transform {
ky: Ratio::zero(),
kx: Ratio::zero(),
sy: Ratio::one(),
tx: Length::zero(),
ty: Length::zero(),
tx: Abs::zero(),
ty: Abs::zero(),
}
}
/// A translate transform.
pub const fn translate(tx: Length, ty: Length) -> Self {
pub const fn translate(tx: Abs, ty: Abs) -> Self {
Self { tx, ty, ..Self::identity() }
}

View File

@ -33,7 +33,7 @@ impl ImageNode {
let height = args.named("height")?;
Ok(Content::inline(
ImageNode(image).pack().sized(Spec::new(width, height)),
ImageNode(image).pack().sized(Axes::new(width, height)),
))
}
}
@ -62,7 +62,7 @@ impl Layout for ImageNode {
} else if first.y.is_finite() {
Size::new(first.x.min(first.y * px_ratio), first.y)
} else {
Size::new(Length::pt(pxw), Length::pt(pxh))
Size::new(Abs::pt(pxw), Abs::pt(pxh))
};
// Compute the actual size of the fitted image.

View File

@ -4,9 +4,9 @@ use crate::library::prelude::*;
#[derive(Debug, Hash)]
pub struct LineNode {
/// Where the line starts.
origin: Spec<Relative<RawLength>>,
origin: Axes<Rel<Length>>,
/// The offset from the `origin` where the line ends.
delta: Spec<Relative<RawLength>>,
delta: Axes<Rel<Length>>,
}
#[node]
@ -18,18 +18,17 @@ impl LineNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let origin = args.named("origin")?.unwrap_or_default();
let delta = match args.named::<Spec<Relative<RawLength>>>("to")? {
let delta = match args.named::<Axes<Rel<Length>>>("to")? {
Some(to) => to.zip(origin).map(|(to, from)| to - from),
None => {
let length = args
.named::<Relative<RawLength>>("length")?
.unwrap_or(Length::cm(1.0).into());
let length =
args.named::<Rel<Length>>("length")?.unwrap_or(Abs::cm(1.0).into());
let angle = args.named::<Angle>("angle")?.unwrap_or_default();
let x = angle.cos() * length;
let y = angle.sin() * length;
Spec::new(x, y)
Axes::new(x, y)
}
};
@ -69,12 +68,12 @@ impl Layout for LineNode {
}
castable! {
Spec<Relative<RawLength>>,
Axes<Rel<Length>>,
Expected: "array of two relative lengths",
Value::Array(array) => {
let mut iter = array.into_iter();
match (iter.next(), iter.next(), iter.next()) {
(Some(a), Some(b), None) => Spec::new(a.cast()?, b.cast()?),
(Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?),
_ => Err("point array must contain exactly two entries")?,
}
},

View File

@ -29,20 +29,19 @@ impl<const S: ShapeKind> ShapeNode<S> {
/// How much to pad the shape's content.
#[property(resolve, fold)]
pub const INSET: Sides<Option<Relative<RawLength>>> = Sides::splat(Relative::zero());
pub const INSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
/// How much to extend the shape's dimensions beyond the allocated space.
#[property(resolve, fold)]
pub const OUTSET: Sides<Option<Relative<RawLength>>> = Sides::splat(Relative::zero());
pub const OUTSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
/// How much to round the shape's corners.
#[property(skip, resolve, fold)]
pub const RADIUS: Corners<Option<Relative<RawLength>>> =
Corners::splat(Relative::zero());
pub const RADIUS: Corners<Option<Rel<Length>>> = Corners::splat(Rel::zero());
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let size = match S {
SQUARE => args.named::<RawLength>("size")?.map(Relative::from),
CIRCLE => args.named::<RawLength>("radius")?.map(|r| 2.0 * Relative::from(r)),
SQUARE => args.named::<Length>("size")?.map(Rel::from),
CIRCLE => args.named::<Length>("radius")?.map(|r| 2.0 * Rel::from(r)),
_ => None,
};
@ -57,7 +56,7 @@ impl<const S: ShapeKind> ShapeNode<S> {
};
Ok(Content::inline(
Self(args.eat()?).pack().sized(Spec::new(width, height)),
Self(args.eat()?).pack().sized(Axes::new(width, height)),
))
}
@ -90,7 +89,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
}
// Pad the child.
let child = child.clone().padded(inset.map(|side| side.map(RawLength::from)));
let child = child.clone().padded(inset.map(|side| side.map(Length::from)));
let mut pod = Regions::one(regions.first, regions.base, regions.expand);
frames = child.layout(world, &pod, styles)?;
@ -112,14 +111,13 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
};
pod.first = Size::splat(length);
pod.expand = Spec::splat(true);
pod.expand = Axes::splat(true);
frames = child.layout(world, &pod, styles)?;
}
} else {
// The default size that a shape takes on if it has no child and
// enough space.
let mut size =
Size::new(Length::pt(45.0), Length::pt(30.0)).min(regions.first);
let mut size = Size::new(Abs::pt(45.0), Abs::pt(30.0)).min(regions.first);
if is_quadratic(S) {
let length = if regions.expand.x || regions.expand.y {
@ -159,16 +157,11 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
if fill.is_some() || stroke.iter().any(Option::is_some) {
if is_round(S) {
let shape = Shape {
geometry: Geometry::Ellipse(size),
fill,
stroke: stroke.left,
};
let shape = ellipse(size, fill, stroke.left);
frame.prepend(pos, Element::Shape(shape));
} else {
frame.prepend_multiple(
RoundedRect::new(size, radius)
.shapes(fill, stroke)
rounded_rect(size, radius, fill, stroke)
.into_iter()
.map(|x| (pos, Element::Shape(x))),
)

View File

@ -5,7 +5,7 @@ use crate::library::text::{HorizontalAlign, ParNode};
#[derive(Debug, Hash)]
pub struct AlignNode {
/// How to align the node horizontally and vertically.
pub aligns: Spec<Option<RawAlign>>,
pub aligns: Axes<Option<RawAlign>>,
/// The node to be aligned.
pub child: LayoutNode,
}
@ -13,11 +13,11 @@ pub struct AlignNode {
#[node]
impl AlignNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let aligns: Spec<Option<RawAlign>> = args.find()?.unwrap_or_default();
let aligns: Axes<Option<RawAlign>> = args.find()?.unwrap_or_default();
let body: Content = args.expect("body")?;
Ok(match (body, aligns) {
(Content::Block(node), _) => Content::Block(node.aligned(aligns)),
(other, Spec { x: Some(x), y: None }) => {
(other, Axes { x: Some(x), y: None }) => {
other.styled(ParNode::ALIGN, HorizontalAlign(x))
}
(other, _) => Content::Block(other.pack().aligned(aligns)),
@ -34,7 +34,7 @@ impl Layout for AlignNode {
) -> SourceResult<Vec<Frame>> {
// The child only needs to expand along an axis if there's no alignment.
let mut pod = regions.clone();
pod.expand &= self.aligns.map_is_none();
pod.expand &= self.aligns.as_ref().map(Option::is_none);
// Align paragraphs inside the child.
let mut passed = StyleMap::new();
@ -51,7 +51,7 @@ impl Layout for AlignNode {
let aligns = self
.aligns
.map(|align| align.resolve(styles))
.unwrap_or(Spec::new(Align::Left, Align::Top));
.unwrap_or(Axes::new(Align::Left, Align::Top));
frame.resize(target, aligns);
}

View File

@ -15,7 +15,7 @@ pub struct ColumnsNode {
impl ColumnsNode {
/// The size of the gutter space between each column.
#[property(resolve)]
pub const GUTTER: Relative<RawLength> = Ratio::new(0.04).into();
pub const GUTTER: Rel<Length> = Ratio::new(0.04).into();
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Content::block(Self {
@ -53,7 +53,7 @@ impl Layout for ColumnsNode {
.skip(1)
.collect(),
last: regions.last,
expand: Spec::new(true, regions.expand.y),
expand: Axes::new(true, regions.expand.y),
};
// Layout the children.
@ -69,9 +69,9 @@ impl Layout for ColumnsNode {
// Otherwise its the maximum column height for the frame. In that
// case, the frame is first created with zero height and then
// resized.
let height = if regions.expand.y { region.y } else { Length::zero() };
let height = if regions.expand.y { region.y } else { Abs::zero() };
let mut output = Frame::new(Size::new(regions.first.x, height));
let mut cursor = Length::zero();
let mut cursor = Abs::zero();
for _ in 0 .. columns {
let frame = match frames.next() {

View File

@ -9,7 +9,7 @@ impl BoxNode {
let width = args.named("width")?;
let height = args.named("height")?;
let body: LayoutNode = args.eat()?.unwrap_or_default();
Ok(Content::inline(body.sized(Spec::new(width, height))))
Ok(Content::inline(body.sized(Axes::new(width, height))))
}
}

View File

@ -81,14 +81,14 @@ pub struct FlowLayouter {
/// The regions to layout children into.
regions: Regions,
/// Whether the flow should expand to fill the region.
expand: Spec<bool>,
expand: Axes<bool>,
/// The full size of `regions.size` that was available before we started
/// subtracting.
full: Size,
/// The size used by the frames for the current region.
used: Size,
/// The sum of fractions in the current region.
fr: Fraction,
fr: Fr,
/// Spacing and layouted nodes.
items: Vec<FlowItem>,
/// Finished frames for previous regions.
@ -98,11 +98,11 @@ pub struct FlowLayouter {
/// A prepared item in a flow layout.
enum FlowItem {
/// Absolute spacing between other items.
Absolute(Length),
Absolute(Abs),
/// Fractional spacing between other items.
Fractional(Fraction),
Fractional(Fr),
/// A frame for a layouted child node and how to align it.
Frame(Frame, Spec<Align>),
Frame(Frame, Axes<Align>),
/// An absolutely placed frame.
Placed(Frame),
}
@ -122,7 +122,7 @@ impl FlowLayouter {
expand,
full,
used: Size::zero(),
fr: Fraction::zero(),
fr: Fr::zero(),
items: vec![],
finished: vec![],
}
@ -169,7 +169,7 @@ impl FlowLayouter {
}
// How to align the node.
let aligns = Spec::new(
let aligns = Axes::new(
// For non-expanding paragraphs it is crucial that we align the
// whole paragraph as it is itself aligned.
styles.get(ParNode::ALIGN),
@ -215,7 +215,7 @@ impl FlowLayouter {
}
let mut output = Frame::new(size);
let mut offset = Length::zero();
let mut offset = Abs::zero();
let mut ruler = Align::Top;
// Place all frames.
@ -245,7 +245,7 @@ impl FlowLayouter {
self.regions.next();
self.full = self.regions.first;
self.used = Size::zero();
self.fr = Fraction::zero();
self.fr = Fr::zero();
self.finished.push(output);
}

View File

@ -4,9 +4,9 @@ use crate::library::prelude::*;
#[derive(Debug, Hash)]
pub struct GridNode {
/// Defines sizing for content rows and columns.
pub tracks: Spec<Vec<TrackSizing>>,
pub tracks: Axes<Vec<TrackSizing>>,
/// Defines sizing of gutter rows and columns between content.
pub gutter: Spec<Vec<TrackSizing>>,
pub gutter: Axes<Vec<TrackSizing>>,
/// The nodes to be arranged in a grid.
pub cells: Vec<LayoutNode>,
}
@ -20,8 +20,8 @@ impl GridNode {
let column_gutter = args.named("column-gutter")?;
let row_gutter = args.named("row-gutter")?;
Ok(Content::block(Self {
tracks: Spec::new(columns, rows),
gutter: Spec::new(
tracks: Axes::new(columns, rows),
gutter: Axes::new(
column_gutter.unwrap_or_else(|| base_gutter.clone()),
row_gutter.unwrap_or(base_gutter),
),
@ -59,10 +59,10 @@ pub enum TrackSizing {
Auto,
/// A track size specified in absolute terms and relative to the parent's
/// size.
Relative(Relative<RawLength>),
Relative(Rel<Length>),
/// A track size specified as a fraction of the remaining free space in the
/// parent.
Fractional(Fraction),
Fractional(Fr),
}
castable! {
@ -105,16 +105,16 @@ pub struct GridLayouter<'a> {
/// The inherited styles.
styles: StyleChain<'a>,
/// Resolved column sizes.
rcols: Vec<Length>,
rcols: Vec<Abs>,
/// Rows in the current region.
lrows: Vec<Row>,
/// The full height of the current region.
full: Length,
full: Abs,
/// The used-up size of the current region. The horizontal size is
/// determined once after columns are resolved and not touched again.
used: Size,
/// The sum of fractions in the current region.
fr: Fraction,
fr: Fr,
/// Frames for finished regions.
finished: Vec<Frame>,
}
@ -125,7 +125,7 @@ enum Row {
/// Finished row frame of auto or relative row.
Frame(Frame),
/// Fractional row with y index.
Fr(Fraction, usize),
Fr(Fr, usize),
}
impl<'a> GridLayouter<'a> {
@ -134,8 +134,8 @@ impl<'a> GridLayouter<'a> {
/// This prepares grid layout by unifying content and gutter tracks.
pub fn new(
world: Tracked<'a, dyn World>,
tracks: Spec<&[TrackSizing]>,
gutter: Spec<&[TrackSizing]>,
tracks: Axes<&[TrackSizing]>,
gutter: Axes<&[TrackSizing]>,
cells: &'a [LayoutNode],
regions: &Regions,
styles: StyleChain<'a>,
@ -156,7 +156,7 @@ impl<'a> GridLayouter<'a> {
};
let auto = TrackSizing::Auto;
let zero = TrackSizing::Relative(Relative::zero());
let zero = TrackSizing::Relative(Rel::zero());
let get_or = |tracks: &[_], idx, default| {
tracks.get(idx).or(tracks.last()).copied().unwrap_or(default)
};
@ -178,13 +178,13 @@ impl<'a> GridLayouter<'a> {
rows.pop();
let full = regions.first.y;
let rcols = vec![Length::zero(); cols.len()];
let rcols = vec![Abs::zero(); cols.len()];
let lrows = vec![];
// We use the regions for auto row measurement. Since at that moment,
// columns are already sized, we can enable horizontal expansion.
let mut regions = regions.clone();
regions.expand = Spec::new(true, false);
regions.expand = Axes::new(true, false);
Self {
world,
@ -197,7 +197,7 @@ impl<'a> GridLayouter<'a> {
lrows,
full,
used: Size::zero(),
fr: Fraction::zero(),
fr: Fr::zero(),
finished: vec![],
}
}
@ -230,10 +230,10 @@ impl<'a> GridLayouter<'a> {
/// Determine all column sizes.
fn measure_columns(&mut self) -> SourceResult<()> {
// Sum of sizes of resolved relative tracks.
let mut rel = Length::zero();
let mut rel = Abs::zero();
// Sum of fractions of all fractional tracks.
let mut fr = Fraction::zero();
let mut fr = Fr::zero();
// Resolve the size of all relative columns and compute the sum of all
// fractional tracks.
@ -252,14 +252,14 @@ impl<'a> GridLayouter<'a> {
// Size that is not used by fixed-size columns.
let available = self.regions.first.x - rel;
if available >= Length::zero() {
if available >= Abs::zero() {
// Determine size of auto columns.
let (auto, count) = self.measure_auto_columns(available)?;
// If there is remaining space, distribute it to fractional columns,
// otherwise shrink auto columns.
let remaining = available - auto;
if remaining >= Length::zero() {
if remaining >= Abs::zero() {
if !fr.is_zero() {
self.grow_fractional_columns(remaining, fr);
}
@ -275,11 +275,8 @@ impl<'a> GridLayouter<'a> {
}
/// Measure the size that is available to auto columns.
fn measure_auto_columns(
&mut self,
available: Length,
) -> SourceResult<(Length, usize)> {
let mut auto = Length::zero();
fn measure_auto_columns(&mut self, available: Abs) -> SourceResult<(Abs, usize)> {
let mut auto = Abs::zero();
let mut count = 0;
// Determine size of auto columns by laying out all cells in those
@ -289,12 +286,12 @@ impl<'a> GridLayouter<'a> {
continue;
}
let mut resolved = Length::zero();
let mut resolved = Abs::zero();
for y in 0 .. self.rows.len() {
if let Some(node) = self.cell(x, y) {
let size = Size::new(available, self.regions.base.y);
let mut pod =
Regions::one(size, self.regions.base, Spec::splat(false));
Regions::one(size, self.regions.base, Axes::splat(false));
// For relative rows, we can already resolve the correct
// base, for auto it's already correct and for fr we could
@ -318,7 +315,7 @@ impl<'a> GridLayouter<'a> {
}
/// Distribute remaining space to fractional columns.
fn grow_fractional_columns(&mut self, remaining: Length, fr: Fraction) {
fn grow_fractional_columns(&mut self, remaining: Abs, fr: Fr) {
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
if let TrackSizing::Fractional(v) = col {
*rcol = v.share(fr, remaining);
@ -327,7 +324,7 @@ impl<'a> GridLayouter<'a> {
}
/// Redistribute space to auto columns so that each gets a fair share.
fn shrink_auto_columns(&mut self, available: Length, count: usize) {
fn shrink_auto_columns(&mut self, available: Abs, count: usize) {
// The fair share each auto column may have.
let fair = available / count as f64;
@ -359,7 +356,7 @@ impl<'a> GridLayouter<'a> {
/// Layout a row with automatic height. Such a row may break across multiple
/// regions.
fn layout_auto_row(&mut self, y: usize) -> SourceResult<()> {
let mut resolved: Vec<Length> = vec![];
let mut resolved: Vec<Abs> = vec![];
// Determine the size for each region of the row.
for (x, &rcol) in self.rcols.iter().enumerate() {
@ -426,11 +423,7 @@ impl<'a> GridLayouter<'a> {
/// Layout a row with relative height. Such a row cannot break across
/// multiple regions, but it may force a region break.
fn layout_relative_row(
&mut self,
v: Relative<RawLength>,
y: usize,
) -> SourceResult<()> {
fn layout_relative_row(&mut self, v: Rel<Length>, y: usize) -> SourceResult<()> {
let resolved = v.resolve(self.styles).relative_to(self.regions.base.y);
let frame = self.layout_single_row(resolved, y)?;
@ -451,7 +444,7 @@ impl<'a> GridLayouter<'a> {
}
/// Layout a row with fixed height and return its frame.
fn layout_single_row(&mut self, height: Length, y: usize) -> SourceResult<Frame> {
fn layout_single_row(&mut self, height: Abs, y: usize) -> SourceResult<Frame> {
let mut output = Frame::new(Size::new(self.used.x, height));
let mut pos = Point::zero();
@ -462,11 +455,11 @@ impl<'a> GridLayouter<'a> {
// Set the base to the region's base for auto rows and to the
// size for relative and fractional rows.
let base = Spec::new(self.cols[x], self.rows[y])
let base = Axes::new(self.cols[x], self.rows[y])
.map(|s| s == TrackSizing::Auto)
.select(self.regions.base, size);
let pod = Regions::one(size, base, Spec::splat(true));
let pod = Regions::one(size, base, Axes::splat(true));
let frame = node.layout(self.world, &pod, self.styles)?.remove(0);
match frame.role() {
Some(Role::ListLabel | Role::ListItemBody) => {
@ -488,7 +481,7 @@ impl<'a> GridLayouter<'a> {
/// Layout a row spanning multiple regions.
fn layout_multi_row(
&mut self,
heights: &[Length],
heights: &[Abs],
y: usize,
) -> SourceResult<Vec<Frame>> {
// Prepare frames.
@ -499,7 +492,7 @@ impl<'a> GridLayouter<'a> {
// Prepare regions.
let size = Size::new(self.used.x, heights[0]);
let mut pod = Regions::one(size, self.regions.base, Spec::splat(true));
let mut pod = Regions::one(size, self.regions.base, Axes::splat(true));
pod.backlog = heights[1 ..].to_vec();
// Layout the row.
@ -573,8 +566,8 @@ impl<'a> GridLayouter<'a> {
self.finished.push(output);
self.regions.next();
self.full = self.regions.first.y;
self.used.y = Length::zero();
self.fr = Fraction::zero();
self.used.y = Abs::zero();
self.fr = Fr::zero();
Ok(())
}

View File

@ -4,7 +4,7 @@ use crate::library::prelude::*;
#[derive(Debug, Hash)]
pub struct PadNode {
/// The amount of padding.
pub padding: Sides<Relative<RawLength>>,
pub padding: Sides<Rel<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<Length>>) -> Size {
fn shrink(size: Size, padding: Sides<Rel<Abs>>) -> Size {
size - padding.relative_to(size).sum_by_axis()
}
@ -77,7 +77,7 @@ fn shrink(size: Size, padding: Sides<Relative<Length>>) -> 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<Length>>) -> Size {
fn grow(size: Size, padding: Sides<Rel<Abs>>) -> Size {
size.zip(padding.sum_by_axis())
.map(|(s, p)| (s + p.abs).safe_div(1.0 - p.rel.get()))
}

View File

@ -11,17 +11,16 @@ pub struct PageNode(pub LayoutNode);
impl PageNode {
/// The unflipped width of the page.
#[property(resolve)]
pub const WIDTH: Smart<RawLength> = Smart::Custom(Paper::A4.width().into());
pub const WIDTH: Smart<Length> = Smart::Custom(Paper::A4.width().into());
/// The unflipped height of the page.
#[property(resolve)]
pub const HEIGHT: Smart<RawLength> = Smart::Custom(Paper::A4.height().into());
pub const HEIGHT: Smart<Length> = Smart::Custom(Paper::A4.height().into());
/// Whether the page is flipped into landscape orientation.
pub const FLIPPED: bool = false;
/// The page's margins.
#[property(fold)]
pub const MARGINS: Sides<Option<Smart<Relative<RawLength>>>> =
Sides::splat(Smart::Auto);
pub const MARGINS: Sides<Option<Smart<Rel<Length>>>> = Sides::splat(Smart::Auto);
/// How many columns the page has.
pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap();
@ -63,8 +62,8 @@ impl PageNode {
) -> SourceResult<Vec<Frame>> {
// When one of the lengths is infinite the page fits its content along
// that axis.
let width = styles.get(Self::WIDTH).unwrap_or(Length::inf());
let height = styles.get(Self::HEIGHT).unwrap_or(Length::inf());
let width = styles.get(Self::WIDTH).unwrap_or(Abs::inf());
let height = styles.get(Self::HEIGHT).unwrap_or(Abs::inf());
let mut size = Size::new(width, height);
if styles.get(Self::FLIPPED) {
std::mem::swap(&mut size.x, &mut size.y);
@ -76,7 +75,7 @@ impl PageNode {
}
// Determine the margins.
let default = Relative::from(0.1190 * min);
let default = Rel::from(0.1190 * min);
let padding = styles.get(Self::MARGINS).map(|side| side.unwrap_or(default));
let mut child = self.0.clone();
@ -96,7 +95,7 @@ impl PageNode {
}
// Layout the child.
let regions = Regions::repeat(size, size, size.map(Length::is_finite));
let regions = Regions::repeat(size, size, size.map(Abs::is_finite));
let mut frames = child.layout(world, &regions, styles)?;
let header = styles.get(Self::HEADER);
@ -127,7 +126,7 @@ impl PageNode {
(Role::Background, background, Point::zero(), size),
] {
if let Some(content) = marginal.resolve(world, page)? {
let pod = Regions::one(area, area, Spec::splat(true));
let pod = Regions::one(area, area, Axes::splat(true));
let mut sub = content.layout(world, &pod, styles)?.remove(0);
sub.apply_role(role);
@ -224,13 +223,13 @@ pub struct Paper {
impl Paper {
/// The width of the paper.
pub fn width(self) -> Length {
Length::mm(self.width)
pub fn width(self) -> Abs {
Abs::mm(self.width)
}
/// The height of the paper.
pub fn height(self) -> Length {
Length::mm(self.height)
pub fn height(self) -> Abs {
Abs::mm(self.height)
}
}

View File

@ -8,12 +8,12 @@ pub struct PlaceNode(pub LayoutNode);
#[node]
impl PlaceNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let aligns = args.find()?.unwrap_or(Spec::with_x(Some(RawAlign::Start)));
let aligns = args.find()?.unwrap_or(Axes::with_x(Some(RawAlign::Start)));
let dx = args.named("dx")?.unwrap_or_default();
let dy = args.named("dy")?.unwrap_or_default();
let body: LayoutNode = args.expect("body")?;
Ok(Content::block(Self(
body.moved(Spec::new(dx, dy)).aligned(aligns),
body.moved(Axes::new(dx, dy)).aligned(aligns),
)))
}
}
@ -30,7 +30,7 @@ impl Layout for PlaceNode {
// The pod is the base area of the region because for absolute
// placement we don't really care about the already used area.
let pod = {
let finite = regions.base.map(Length::is_finite);
let finite = regions.base.map(Abs::is_finite);
let expand = finite & (regions.expand | out_of_flow);
Regions::one(regions.base, regions.base, expand)
};

View File

@ -31,10 +31,10 @@ impl VNode {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Spacing {
/// Spacing specified in absolute terms and relative to the parent's size.
Relative(Relative<RawLength>),
Relative(Rel<Length>),
/// Spacing specified as a fraction of the remaining free space in the
/// parent.
Fractional(Fraction),
Fractional(Fr),
}
impl Spacing {
@ -44,9 +44,9 @@ impl Spacing {
}
}
impl From<Length> for Spacing {
fn from(length: Length) -> Self {
Self::Relative(length.into())
impl From<Abs> for Spacing {
fn from(abs: Abs) -> Self {
Self::Relative(abs.into())
}
}
@ -71,12 +71,12 @@ castable! {
/// Spacing around and between block-level nodes, relative to paragraph spacing.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct BlockSpacing(Relative<RawLength>);
pub struct BlockSpacing(Rel<Length>);
castable!(BlockSpacing: Relative<RawLength>);
castable!(BlockSpacing: Rel<Length>);
impl Resolve for BlockSpacing {
type Output = Length;
type Output = Abs;
fn resolve(self, styles: StyleChain) -> Self::Output {
let whole = styles.get(ParNode::SPACING);

View File

@ -90,19 +90,19 @@ pub struct StackLayouter<'a> {
/// The stacking direction.
dir: Dir,
/// The axis of the stacking direction.
axis: SpecAxis,
axis: Axis,
/// The regions to layout children into.
regions: Regions,
/// The inherited styles.
styles: StyleChain<'a>,
/// Whether the stack itself should expand to fill the region.
expand: Spec<bool>,
expand: Axes<bool>,
/// The full size of the current region that was available at the start.
full: Size,
/// The generic size used by the frames for the current region.
used: Gen<Length>,
used: Gen<Abs>,
/// The sum of fractions in the current region.
fr: Fraction,
fr: Fr,
/// Already layouted items whose exact positions are not yet known due to
/// fractional spacing.
items: Vec<StackItem>,
@ -113,9 +113,9 @@ pub struct StackLayouter<'a> {
/// A prepared item in a stack layout.
enum StackItem {
/// Absolute spacing between other items.
Absolute(Length),
Absolute(Abs),
/// Fractional spacing between other items.
Fractional(Fraction),
Fractional(Fr),
/// A frame for a layouted child node.
Frame(Frame, Align),
}
@ -139,7 +139,7 @@ impl<'a> StackLayouter<'a> {
expand,
full,
used: Gen::zero(),
fr: Fraction::zero(),
fr: Fr::zero(),
items: vec![],
finished: vec![],
}
@ -200,7 +200,12 @@ impl<'a> StackLayouter<'a> {
frame.apply_role(Role::GenericBlock);
// Grow our size, shrink the region and save the frame for later.
let size = frame.size().to_gen(self.axis);
let size = frame.size();
let size = match self.axis {
Axis::X => Gen::new(size.y, size.x),
Axis::Y => Gen::new(size.x, size.y),
};
self.used.main += size.main;
self.used.cross.set_max(size.cross);
*self.regions.first.get_mut(self.axis) -= size.main;
@ -218,7 +223,7 @@ impl<'a> StackLayouter<'a> {
pub fn finish_region(&mut self) {
// Determine the size of the stack in this region dependening on whether
// the region expands.
let used = self.used.to_spec(self.axis);
let used = self.used.to_axes(self.axis);
let mut size = self.expand.select(self.full, used);
// Expand fully if there are fr spacings.
@ -230,7 +235,7 @@ impl<'a> StackLayouter<'a> {
}
let mut output = Frame::new(size);
let mut cursor = Length::zero();
let mut cursor = Abs::zero();
let mut ruler: Align = self.dir.start().into();
// Place all frames.
@ -255,7 +260,7 @@ impl<'a> StackLayouter<'a> {
self.used.main - child - cursor
};
let pos = Gen::new(Length::zero(), block).to_point(self.axis);
let pos = Gen::new(Abs::zero(), block).to_point(self.axis);
cursor += child;
output.push_frame(pos, frame);
}
@ -266,7 +271,7 @@ impl<'a> StackLayouter<'a> {
self.regions.next();
self.full = self.regions.first;
self.used = Gen::zero();
self.fr = Fraction::zero();
self.fr = Fr::zero();
self.finished.push(output);
}
@ -276,3 +281,39 @@ impl<'a> StackLayouter<'a> {
self.finished
}
}
/// A container with a main and cross component.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Gen<T> {
/// The main component.
pub cross: T,
/// The cross component.
pub main: T,
}
impl<T> Gen<T> {
/// Create a new instance from the two components.
pub const fn new(cross: T, main: T) -> Self {
Self { cross, main }
}
/// Convert to the specific representation, given the current main axis.
pub fn to_axes(self, main: Axis) -> Axes<T> {
match main {
Axis::X => Axes::new(self.main, self.cross),
Axis::Y => Axes::new(self.cross, self.main),
}
}
}
impl Gen<Abs> {
/// The zero value.
pub fn zero() -> Self {
Self { cross: Abs::zero(), main: Abs::zero() }
}
/// Convert to a point.
pub fn to_point(self, main: Axis) -> Point {
self.to_axes(main).to_point()
}
}

View File

@ -5,7 +5,7 @@ use crate::library::prelude::*;
#[derive(Debug, Hash)]
pub struct MoveNode {
/// The offset by which to move the node.
pub delta: Spec<Relative<RawLength>>,
pub delta: Axes<Rel<Length>>,
/// The node whose contents should be moved.
pub child: LayoutNode,
}
@ -16,7 +16,7 @@ impl MoveNode {
let dx = args.named("dx")?.unwrap_or_default();
let dy = args.named("dy")?.unwrap_or_default();
Ok(Content::inline(Self {
delta: Spec::new(dx, dy),
delta: Axes::new(dx, dy),
child: args.expect("body")?,
}))
}
@ -60,7 +60,7 @@ pub type ScaleNode = TransformNode<SCALE>;
impl<const T: TransformKind> TransformNode<T> {
/// The origin of the transformation.
#[property(resolve)]
pub const ORIGIN: Spec<Option<RawAlign>> = Spec::default();
pub const ORIGIN: Axes<Option<RawAlign>> = Axes::default();
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
let transform = match T {
@ -94,7 +94,7 @@ impl<const T: TransformKind> Layout for TransformNode<T> {
let mut frames = self.child.layout(world, regions, styles)?;
for frame in &mut frames {
let Spec { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
let transform = Transform::translate(x, y)
.pre_concat(self.transform)
.pre_concat(Transform::translate(-x, -y));

View File

@ -92,7 +92,7 @@ impl Show for MathNode {
Ok(if self.display() {
Content::block(
LayoutNode::new(self.clone())
.aligned(Spec::with_x(Some(Align::Center.into()))),
.aligned(Axes::with_x(Some(Align::Center.into()))),
)
} else {
Content::inline(self.clone())
@ -171,9 +171,9 @@ fn layout_tex(
// Determine the metrics.
let (x0, y0, x1, y1) = renderer.size(&layout);
let width = Length::pt(x1 - x0);
let mut top = Length::pt(y1);
let mut bottom = Length::pt(-y0);
let width = Abs::pt(x1 - x0);
let mut top = Abs::pt(y1);
let mut bottom = Abs::pt(-y0);
if style != Style::Display {
let metrics = font.metrics();
top = styles.get(TextNode::TOP_EDGE).resolve(styles, metrics);
@ -204,7 +204,7 @@ fn layout_tex(
/// A ReX rendering backend that renders into a frame.
struct FrameBackend {
frame: Frame,
baseline: Length,
baseline: Abs,
font: Font,
fill: Paint,
lang: Lang,
@ -222,7 +222,7 @@ impl FrameBackend {
/// Convert a cursor to a point.
fn transform(&self, cursor: Cursor) -> Point {
Point::new(Length::pt(cursor.x), self.baseline + Length::pt(cursor.y))
Point::new(Abs::pt(cursor.x), self.baseline + Abs::pt(cursor.y))
}
}
@ -232,7 +232,7 @@ impl Backend for FrameBackend {
self.transform(pos),
Element::Text(Text {
font: self.font.clone(),
size: Length::pt(scale),
size: Abs::pt(scale),
fill: self.fill(),
lang: self.lang,
glyphs: vec![Glyph {
@ -249,10 +249,7 @@ impl Backend for FrameBackend {
self.frame.push(
self.transform(pos),
Element::Shape(Shape {
geometry: Geometry::Rect(Size::new(
Length::pt(width),
Length::pt(height),
)),
geometry: Geometry::Rect(Size::new(Abs::pt(width), Abs::pt(height))),
fill: Some(self.fill()),
stroke: None,
}),

View File

@ -218,17 +218,17 @@ impl StyleMapExt for StyleMap {
/// Additional methods for layout nodes.
pub trait LayoutNodeExt {
/// Set alignments for this node.
fn aligned(self, aligns: Spec<Option<RawAlign>>) -> Self;
fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self;
/// Pad this node at the sides.
fn padded(self, padding: Sides<Relative<RawLength>>) -> Self;
fn padded(self, padding: Sides<Rel<Length>>) -> Self;
/// Transform this node's contents without affecting layout.
fn moved(self, delta: Spec<Relative<RawLength>>) -> Self;
fn moved(self, delta: Axes<Rel<Length>>) -> Self;
}
impl LayoutNodeExt for LayoutNode {
fn aligned(self, aligns: Spec<Option<RawAlign>>) -> Self {
fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self {
if aligns.any(Option::is_some) {
layout::AlignNode { aligns, child: self }.pack()
} else {
@ -236,7 +236,7 @@ impl LayoutNodeExt for LayoutNode {
}
}
fn padded(self, padding: Sides<Relative<RawLength>>) -> Self {
fn padded(self, padding: Sides<Rel<Length>>) -> Self {
if !padding.left.is_zero()
|| !padding.top.is_zero()
|| !padding.right.is_zero()
@ -248,7 +248,7 @@ impl LayoutNodeExt for LayoutNode {
}
}
fn moved(self, delta: Spec<Relative<RawLength>>) -> Self {
fn moved(self, delta: Axes<Rel<Length>>) -> Self {
if delta.any(|r| !r.is_zero()) {
layout::MoveNode { delta, child: self }.pack()
} else {

View File

@ -16,12 +16,9 @@ pub use crate::diag::{
pub use crate::frame::*;
pub use crate::geom::*;
pub use crate::model::{
Arg, Args, Array, Cast, Dict, Dynamic, Func, Node, RawAlign, RawLength, RawStroke,
Scope, Smart, Str, Value, Vm,
};
pub use crate::model::{
Content, Fold, Key, Layout, LayoutNode, Regions, Resolve, Selector, Show, ShowNode,
StyleChain, StyleMap, StyleVec,
Arg, Args, Array, Cast, Content, Dict, Dynamic, Fold, Func, Key, Layout, LayoutNode,
Node, RawAlign, RawStroke, Regions, Resolve, Scope, Selector, Show, ShowNode, Smart,
Str, StyleChain, StyleMap, StyleVec, Value, Vm,
};
pub use crate::syntax::{Span, Spanned};
pub use crate::util::EcoString;

View File

@ -29,10 +29,10 @@ impl<const L: ListKind> ListNode<L> {
pub const LABEL: Label = Label::Default;
/// The indentation of each item's label.
#[property(resolve)]
pub const INDENT: RawLength = RawLength::zero();
pub const INDENT: Length = Length::zero();
/// The space between the label and the body of each item.
#[property(resolve)]
pub const BODY_INDENT: RawLength = Em::new(match L {
pub const BODY_INDENT: Length = Em::new(match L {
LIST | ENUM => 0.5,
DESC | _ => 1.0,
})
@ -159,13 +159,13 @@ impl<const L: ListKind> Show for ListNode<L> {
}
Ok(Content::block(GridNode {
tracks: Spec::with_x(vec![
tracks: Axes::with_x(vec![
TrackSizing::Relative(indent.into()),
TrackSizing::Auto,
TrackSizing::Relative(body_indent.into()),
TrackSizing::Auto,
]),
gutter: Spec::with_y(vec![TrackSizing::Relative(gutter.into())]),
gutter: Axes::with_y(vec![TrackSizing::Relative(gutter.into())]),
cells,
}))
}

View File

@ -5,9 +5,9 @@ use crate::library::prelude::*;
#[derive(Debug, Hash)]
pub struct TableNode {
/// Defines sizing for content rows and columns.
pub tracks: Spec<Vec<TrackSizing>>,
pub tracks: Axes<Vec<TrackSizing>>,
/// Defines sizing of gutter rows and columns between content.
pub gutter: Spec<Vec<TrackSizing>>,
pub gutter: Axes<Vec<TrackSizing>>,
/// The nodes to be arranged in the table.
pub cells: Vec<Content>,
}
@ -21,7 +21,7 @@ impl TableNode {
#[property(resolve, fold)]
pub const STROKE: Option<RawStroke> = Some(RawStroke::default());
/// How much to pad the cells's content.
pub const PADDING: Relative<RawLength> = Length::pt(5.0).into();
pub const PADDING: Rel<Length> = Abs::pt(5.0).into();
/// The spacing above the table.
#[property(resolve, shorthand(around))]
@ -37,8 +37,8 @@ impl TableNode {
let column_gutter = args.named("column-gutter")?;
let row_gutter = args.named("row-gutter")?;
Ok(Content::show(Self {
tracks: Spec::new(columns, rows),
gutter: Spec::new(
tracks: Axes::new(columns, rows),
gutter: Axes::new(
column_gutter.unwrap_or_else(|| base_gutter.clone()),
row_gutter.unwrap_or(base_gutter),
),

View File

@ -26,10 +26,10 @@ impl<const L: DecoLine> DecoNode<L> {
/// Position of the line relative to the baseline, read from the font tables
/// if `auto`.
#[property(resolve)]
pub const OFFSET: Smart<RawLength> = Smart::Auto;
pub const OFFSET: Smart<Length> = Smart::Auto;
/// Amount that the line will be longer or shorter than its associated text.
#[property(resolve)]
pub const EXTENT: RawLength = RawLength::zero();
pub const EXTENT: Length = Length::zero();
/// Whether the line skips sections in which it would collide
/// with the glyphs. Does not apply to strikethrough.
pub const EVADE: bool = true;
@ -69,9 +69,9 @@ impl<const L: DecoLine> Show for DecoNode<L> {
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Decoration {
pub line: DecoLine,
pub stroke: RawStroke<Length>,
pub offset: Smart<Length>,
pub extent: Length,
pub stroke: RawStroke<Abs>,
pub offset: Smart<Abs>,
pub extent: Abs,
pub evade: bool,
}
@ -92,9 +92,9 @@ pub fn decorate(
frame: &mut Frame,
deco: &Decoration,
text: &Text,
shift: Length,
shift: Abs,
pos: Point,
width: Length,
width: Abs,
) {
let font_metrics = text.font.metrics();
let metrics = match deco.line {
@ -116,9 +116,9 @@ pub fn decorate(
let mut start = pos.x - deco.extent;
let end = pos.x + (width + 2.0 * deco.extent);
let mut push_segment = |from: Length, to: Length| {
let mut push_segment = |from: Abs, to: Abs| {
let origin = Point::new(from, pos.y + offset);
let target = Point::new(to - from, Length::zero());
let target = Point::new(to - from, Abs::zero());
if target.x >= min_width || !evade {
let shape = Geometry::Line(target).stroked(stroke);
@ -161,7 +161,7 @@ pub fn decorate(
intersections.extend(
path.segments()
.flat_map(|seg| seg.intersect_line(line))
.map(|is| Length::raw(line.eval(is.line_t).x)),
.map(|is| Abs::raw(line.eval(is.line_t).x)),
);
}
}
@ -196,12 +196,12 @@ pub fn decorate(
struct BezPathBuilder {
path: BezPath,
units_per_em: f64,
font_size: Length,
font_size: Abs,
x_offset: f64,
}
impl BezPathBuilder {
fn new(units_per_em: f64, font_size: Length, x_offset: f64) -> Self {
fn new(units_per_em: f64, font_size: Abs, x_offset: f64) -> Self {
Self {
path: BezPath::new(),
units_per_em,

View File

@ -43,9 +43,9 @@ castable! {
Value::Str(string) => Self::Url(string.into()),
Value::Dict(dict) => {
let page = dict.get("page")?.clone().cast()?;
let x: RawLength = dict.get("x")?.clone().cast()?;
let y: RawLength = dict.get("y")?.clone().cast()?;
Self::Internal(Location { page, pos: Point::new(x.length, y.length) })
let x: Length = dict.get("x")?.clone().cast()?;
let y: Length = dict.get("y")?.clone().cast()?;
Self::Internal(Location { page, pos: Point::new(x.abs, y.abs) })
},
}

View File

@ -48,19 +48,19 @@ impl TextNode {
/// The size of the glyphs.
#[property(shorthand, fold)]
pub const SIZE: TextSize = Length::pt(11.0);
pub const SIZE: TextSize = Abs::pt(11.0);
/// The glyph fill color.
#[property(shorthand)]
pub const FILL: Paint = Color::BLACK.into();
/// The amount of space that should be added between characters.
#[property(resolve)]
pub const TRACKING: RawLength = RawLength::zero();
pub const TRACKING: Length = Length::zero();
/// The width of spaces relative to the font's space width.
#[property(resolve)]
pub const SPACING: Relative<RawLength> = Relative::one();
pub const SPACING: Rel<Length> = Rel::one();
/// The offset of the baseline.
#[property(resolve)]
pub const BASELINE: RawLength = RawLength::zero();
pub const BASELINE: Length = Length::zero();
/// Whether certain glyphs can hang over into the margin.
pub const OVERHANG: bool = true;
/// The top end of the text bounding box.
@ -243,17 +243,17 @@ castable! {
/// The size of text.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct TextSize(pub RawLength);
pub struct TextSize(pub Length);
impl Fold for TextSize {
type Output = Length;
type Output = Abs;
fn fold(self, outer: Self::Output) -> Self::Output {
self.0.em.at(outer) + self.0.length
self.0.em.at(outer) + self.0.abs
}
}
castable!(TextSize: RawLength);
castable!(TextSize: Length);
/// Specifies the bottom or top edge of text.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
@ -261,12 +261,12 @@ pub enum TextEdge {
/// An edge specified using one of the well-known font metrics.
Metric(VerticalFontMetric),
/// An edge specified as a length.
Length(RawLength),
Length(Length),
}
impl TextEdge {
/// Resolve the value of the text edge given a font's metrics.
pub fn resolve(self, styles: StyleChain, metrics: &FontMetrics) -> Length {
pub fn resolve(self, styles: StyleChain, metrics: &FontMetrics) -> Abs {
match self {
Self::Metric(metric) => metrics.vertical(metric).resolve(styles),
Self::Length(length) => length.resolve(styles),
@ -310,8 +310,8 @@ castable! {
HorizontalDir,
Expected: "direction",
@dir: Dir => match dir.axis() {
SpecAxis::Horizontal => Self(*dir),
SpecAxis::Vertical => Err("must be horizontal")?,
Axis::X => Self(*dir),
Axis::Y => Err("must be horizontal")?,
},
}

View File

@ -30,13 +30,13 @@ pub enum ParChild {
impl ParNode {
/// The spacing between lines.
#[property(resolve)]
pub const LEADING: RawLength = Em::new(0.65).into();
pub const LEADING: Length = Em::new(0.65).into();
/// The extra spacing between paragraphs.
#[property(resolve)]
pub const SPACING: RawLength = Em::new(1.2).into();
pub const SPACING: Length = Em::new(1.2).into();
/// The indent the first line of a consecutive paragraph should have.
#[property(resolve)]
pub const INDENT: RawLength = RawLength::zero();
pub const INDENT: Length = Length::zero();
/// Whether to allow paragraph spacing when there is paragraph indent.
pub const SPACING_AND_INDENT: bool = false;
@ -119,8 +119,8 @@ castable! {
HorizontalAlign,
Expected: "alignment",
@align: RawAlign => match align.axis() {
SpecAxis::Horizontal => Self(*align),
SpecAxis::Vertical => Err("must be horizontal")?,
Axis::X => Self(*align),
Axis::Y => Err("must be horizontal")?,
},
}
@ -212,7 +212,7 @@ struct Preparation<'a> {
/// The text language if it's the same for all children.
lang: Option<Lang>,
/// The resolved leading between lines.
leading: Length,
leading: Abs,
/// The paragraph's resolved alignment.
align: Align,
/// Whether to justify the paragraph.
@ -292,9 +292,9 @@ enum Item<'a> {
/// A shaped text run with consistent style and direction.
Text(ShapedText<'a>),
/// Absolute spacing between other items.
Absolute(Length),
Absolute(Abs),
/// Fractional spacing between other items.
Fractional(Fraction),
Fractional(Fr),
/// A layouted child node.
Frame(Frame),
/// A repeating node that fills the remaining space.
@ -320,12 +320,12 @@ impl<'a> Item<'a> {
}
/// The natural layouted width of the item.
fn width(&self) -> Length {
fn width(&self) -> Abs {
match self {
Self::Text(shaped) => shaped.width,
Self::Absolute(v) => *v,
Self::Frame(frame) => frame.width(),
Self::Fractional(_) | Self::Repeat(_, _) => Length::zero(),
Self::Fractional(_) | Self::Repeat(_, _) => Abs::zero(),
}
}
}
@ -354,7 +354,7 @@ struct Line<'a> {
/// there is only one text item, this takes precedence over `first`.
last: Option<Item<'a>>,
/// The width of the line.
width: Length,
width: Abs,
/// Whether the line should be justified.
justify: bool,
/// Whether the line ends with a hyphen or dash, either naturally or through
@ -402,8 +402,8 @@ impl<'a> Line<'a> {
}
/// How much of the line is stretchable spaces.
fn stretch(&self) -> Length {
let mut stretch = Length::zero();
fn stretch(&self) -> Abs {
let mut stretch = Abs::zero();
for shaped in self.items().filter_map(Item::text) {
stretch += shaped.stretch();
}
@ -411,11 +411,11 @@ impl<'a> Line<'a> {
}
/// The sum of fractions in the line.
fn fr(&self) -> Fraction {
fn fr(&self) -> Fr {
self.items()
.filter_map(|item| match item {
Item::Fractional(fr) => Some(*fr),
Item::Repeat(_, _) => Some(Fraction::one()),
Item::Repeat(_, _) => Some(Fr::one()),
_ => None,
})
.sum()
@ -533,7 +533,7 @@ fn prepare<'a>(
items.push(Item::Repeat(repeat, styles));
} else {
let size = Size::new(regions.first.x, regions.base.y);
let pod = Regions::one(size, regions.base, Spec::splat(false));
let pod = Regions::one(size, regions.base, Axes::splat(false));
let mut frame = node.layout(world, &pod, styles)?.remove(0);
frame.translate(Point::with_y(styles.get(TextNode::BASELINE)));
frame.apply_role(Role::GenericInline);
@ -628,7 +628,7 @@ fn shared_get<'a, K: Key<'a>>(
fn linebreak<'a>(
p: &'a Preparation<'a>,
world: Tracked<dyn World>,
width: Length,
width: Abs,
) -> Vec<Line<'a>> {
match p.styles.get(ParNode::LINEBREAKS) {
Linebreaks::Simple => linebreak_simple(p, world, width),
@ -642,7 +642,7 @@ fn linebreak<'a>(
fn linebreak_simple<'a>(
p: &'a Preparation<'a>,
world: Tracked<dyn World>,
width: Length,
width: Abs,
) -> Vec<Line<'a>> {
let mut lines = vec![];
let mut start = 0;
@ -702,7 +702,7 @@ fn linebreak_simple<'a>(
fn linebreak_optimized<'a>(
p: &'a Preparation<'a>,
world: Tracked<dyn World>,
width: Length,
width: Abs,
) -> Vec<Line<'a>> {
/// The cost of a line or paragraph layout.
type Cost = f64;
@ -930,7 +930,7 @@ fn line<'a>(
first: None,
inner: &[],
last: None,
width: Length::zero(),
width: Abs::zero(),
justify,
dash: false,
};
@ -938,7 +938,7 @@ fn line<'a>(
// Slice out the relevant items.
let (expanded, mut inner) = p.slice(range.clone());
let mut width = Length::zero();
let mut width = Abs::zero();
// Reshape the last item if it's split in half or hyphenated.
let mut last = None;
@ -1075,10 +1075,10 @@ fn commit(
world: Tracked<dyn World>,
line: &Line,
regions: &Regions,
width: Length,
width: Abs,
) -> SourceResult<Frame> {
let mut remaining = width - line.width;
let mut offset = Length::zero();
let mut offset = Abs::zero();
// Reorder the line from logical to visual order.
let reordered = reorder(line);
@ -1112,22 +1112,22 @@ fn commit(
// Determine how much to justify each space.
let fr = line.fr();
let mut justification = Length::zero();
if remaining < Length::zero() || (line.justify && fr.is_zero()) {
let mut justification = Abs::zero();
if remaining < Abs::zero() || (line.justify && fr.is_zero()) {
let justifiables = line.justifiables();
if justifiables > 0 {
justification = remaining / justifiables as f64;
remaining = Length::zero();
remaining = Abs::zero();
}
}
let mut top = Length::zero();
let mut bottom = Length::zero();
let mut top = Abs::zero();
let mut bottom = Abs::zero();
// Build the frames and determine the height and baseline.
let mut frames = vec![];
for item in reordered {
let mut push = |offset: &mut Length, frame: Frame| {
let mut push = |offset: &mut Abs, frame: Frame| {
let width = frame.width();
top.set_max(frame.baseline());
bottom.set_max(frame.size().y - frame.baseline());
@ -1151,9 +1151,9 @@ fn commit(
}
Item::Repeat(node, styles) => {
let before = offset;
let fill = Fraction::one().share(fr, remaining);
let fill = Fr::one().share(fr, remaining);
let size = Size::new(fill, regions.base.y);
let pod = Regions::one(size, regions.base, Spec::new(false, false));
let pod = Regions::one(size, regions.base, Axes::new(false, false));
let frame = node.layout(world, &pod, *styles)?.remove(0);
let width = frame.width();
let count = (fill / width).floor();
@ -1162,7 +1162,7 @@ fn commit(
if count == 1.0 {
offset += p.align.position(remaining);
}
if width > Length::zero() {
if width > Abs::zero() {
for _ in 0 .. (count as usize).min(1000) {
push(&mut offset, frame.clone());
offset += apart;
@ -1175,7 +1175,7 @@ fn commit(
// Remaining space is distributed now.
if !fr.is_zero() {
remaining = Length::zero();
remaining = Abs::zero();
}
let size = Size::new(width, top + bottom);

View File

@ -23,9 +23,9 @@ pub struct ShapedText<'a> {
/// The font variant.
pub variant: FontVariant,
/// The font size.
pub size: Length,
pub size: Abs,
/// The width of the text's bounding box.
pub width: Length,
pub width: Abs,
/// The shaped glyphs.
pub glyphs: Cow<'a, [ShapedGlyph]>,
}
@ -80,11 +80,11 @@ impl<'a> ShapedText<'a> {
///
/// The `justification` defines how much extra advance width each
/// [justifiable glyph](ShapedGlyph::is_justifiable) will get.
pub fn build(&self, world: Tracked<dyn World>, justification: Length) -> Frame {
pub fn build(&self, world: Tracked<dyn World>, justification: Abs) -> Frame {
let (top, bottom) = self.measure(world);
let size = Size::new(self.width, top + bottom);
let mut offset = Length::zero();
let mut offset = Abs::zero();
let mut frame = Frame::new(size);
frame.set_baseline(top);
@ -144,9 +144,9 @@ impl<'a> ShapedText<'a> {
}
/// Measure the top and bottom extent of this text.
fn measure(&self, world: Tracked<dyn World>) -> (Length, Length) {
let mut top = Length::zero();
let mut bottom = Length::zero();
fn measure(&self, world: Tracked<dyn World>) -> (Abs, Abs) {
let mut top = Abs::zero();
let mut bottom = Abs::zero();
let top_edge = self.styles.get(TextNode::TOP_EDGE);
let bottom_edge = self.styles.get(TextNode::BOTTOM_EDGE);
@ -186,7 +186,7 @@ impl<'a> ShapedText<'a> {
}
/// The width of the spaces in the text.
pub fn stretch(&self) -> Length {
pub fn stretch(&self) -> Abs {
self.glyphs
.iter()
.filter(|g| g.is_justifiable())
@ -310,7 +310,7 @@ struct ShapingContext<'a> {
glyphs: Vec<ShapedGlyph>,
used: Vec<Font>,
styles: StyleChain<'a>,
size: Length,
size: Abs,
variant: FontVariant,
tags: Vec<rustybuzz::Feature>,
fallback: bool,

View File

@ -23,7 +23,7 @@ impl<const S: ScriptKind> ShiftNode<S> {
/// font.
pub const TYPOGRAPHIC: bool = true;
/// The baseline shift for synthetic sub- and superscripts.
pub const BASELINE: RawLength =
pub const BASELINE: Length =
Em::new(if S == SUPERSCRIPT { -0.5 } else { 0.2 }).into();
/// The font size for synthetic sub- and superscripts.
pub const SIZE: TextSize = TextSize(Em::new(0.6).into());

View File

@ -15,7 +15,7 @@ use super::{
};
use crate::diag::{SourceResult, StrResult};
use crate::frame::{Frame, Role};
use crate::geom::{Length, Numeric};
use crate::geom::{Abs, Numeric};
use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing};
use crate::library::structure::{DocNode, ListItem, ListNode, DESC, ENUM, LIST};
use crate::library::text::{ParChild, ParNode};
@ -175,7 +175,7 @@ impl Content {
}
/// Add weak vertical spacing above and below the node.
pub fn spaced(self, above: Option<Length>, below: Option<Length>) -> Self {
pub fn spaced(self, above: Option<Abs>, below: Option<Abs>) -> Self {
if above.is_none() && below.is_none() {
return self;
}

View File

@ -11,7 +11,7 @@ use super::{
Pattern, Recipe, Scope, Scopes, StyleEntry, StyleMap, Value, Vm,
};
use crate::diag::{At, SourceResult, StrResult, Trace, Tracepoint};
use crate::geom::{Angle, Em, Fraction, Length, Ratio};
use crate::geom::{Abs, Angle, Em, Fr, Ratio};
use crate::library;
use crate::syntax::ast::TypedNode;
use crate::syntax::{ast, SourceId, Span, Spanned, Unit};
@ -465,10 +465,10 @@ impl Eval for ast::Lit {
ast::LitKind::Int(v) => Value::Int(v),
ast::LitKind::Float(v) => Value::Float(v),
ast::LitKind::Numeric(v, unit) => match unit {
Unit::Length(unit) => Length::with_unit(v, unit).into(),
Unit::Length(unit) => Abs::with_unit(v, unit).into(),
Unit::Angle(unit) => Angle::with_unit(v, unit).into(),
Unit::Em => Em::new(v).into(),
Unit::Fr => Fraction::new(v).into(),
Unit::Fr => Fr::new(v).into(),
Unit::Percent => Ratio::new(v / 100.0).into(),
},
ast::LitKind::Str(v) => Value::Str(v.into()),

View File

@ -8,10 +8,10 @@ use std::sync::Arc;
use comemo::{Prehashed, Tracked};
use super::{Barrier, NodeId, Resolve, StyleChain, StyleEntry};
use super::{Builder, Content, RawLength, Scratch};
use super::{Builder, Content, Scratch};
use crate::diag::SourceResult;
use crate::frame::{Element, Frame};
use crate::geom::{Align, Geometry, Length, Paint, Point, Relative, Size, Spec, Stroke};
use crate::geom::{Abs, Align, Axes, Geometry, Length, Paint, Point, Rel, Size, Stroke};
use crate::World;
/// Layout content into a collection of pages.
@ -56,18 +56,18 @@ pub struct Regions {
/// The base size for relative sizing.
pub base: Size,
/// The height of followup regions. The width is the same for all regions.
pub backlog: Vec<Length>,
pub backlog: Vec<Abs>,
/// The height of the final region that is repeated once the backlog is
/// drained. The width is the same for all regions.
pub last: Option<Length>,
pub last: Option<Abs>,
/// Whether nodes should expand to fill the regions instead of shrinking to
/// fit the content.
pub expand: Spec<bool>,
pub expand: Axes<bool>,
}
impl Regions {
/// Create a new region sequence with exactly one region.
pub fn one(size: Size, base: Size, expand: Spec<bool>) -> Self {
pub fn one(size: Size, base: Size, expand: Axes<bool>) -> Self {
Self {
first: size,
base,
@ -78,7 +78,7 @@ impl Regions {
}
/// Create a new sequence of same-size regions that repeats indefinitely.
pub fn repeat(size: Size, base: Size, expand: Spec<bool>) -> Self {
pub fn repeat(size: Size, base: Size, expand: Axes<bool>) -> Self {
Self {
first: size,
base,
@ -108,7 +108,7 @@ impl Regions {
/// Whether the first region is full and a region break is called for.
pub fn is_full(&self) -> bool {
Length::zero().fits(self.first.y) && !self.in_last()
Abs::zero().fits(self.first.y) && !self.in_last()
}
/// Whether the first region is the last usable region.
@ -173,7 +173,7 @@ impl LayoutNode {
}
/// Force a size for this node.
pub fn sized(self, sizing: Spec<Option<Relative<RawLength>>>) -> Self {
pub fn sized(self, sizing: Axes<Option<Rel<Length>>>) -> Self {
if sizing.any(Option::is_some) {
SizedNode { sizing, child: self }.pack()
} else {
@ -279,7 +279,7 @@ impl Layout for EmptyNode {
#[derive(Debug, Hash)]
struct SizedNode {
/// How to size the node horizontally and vertically.
sizing: Spec<Option<Relative<RawLength>>>,
sizing: Axes<Option<Rel<Length>>>,
/// The node to be sized.
child: LayoutNode,
}
@ -303,7 +303,7 @@ impl Layout for SizedNode {
// Select the appropriate base and expansion for the child depending
// on whether it is automatically or relatively sized.
let is_auto = self.sizing.map_is_none();
let is_auto = self.sizing.as_ref().map(Option::is_none);
let base = is_auto.select(regions.base, size);
let expand = regions.expand | !is_auto;

View File

@ -2,9 +2,9 @@
use std::cmp::Ordering;
use super::{RawAlign, RawLength, RawStroke, Regex, Smart, Value};
use super::{RawAlign, RawStroke, Regex, Smart, Value};
use crate::diag::StrResult;
use crate::geom::{Numeric, Relative, Spec, SpecAxis};
use crate::geom::{Axes, Axis, Length, Numeric, Rel};
use Value::*;
/// Bail with a type mismatch error.
@ -106,8 +106,8 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
{
if a.axis() != b.axis() {
Value::dynamic(match a.axis() {
SpecAxis::Horizontal => Spec { x: a, y: b },
SpecAxis::Vertical => Spec { x: b, y: a },
Axis::X => Axes { x: a, y: b },
Axis::Y => Axes { x: b, y: a },
})
} else {
return Err(format!("cannot add two {:?} alignments", a.axis()));
@ -203,8 +203,8 @@ pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> {
(Length(a), Int(b)) => Length(a / b as f64),
(Length(a), Float(b)) => Length(a / b),
(Length(a), Length(b)) => Float(div_length(a, b)?),
(Length(a), Relative(b)) if b.rel.is_zero() => Float(div_length(a, b.abs)?),
(Length(a), Length(b)) => Float(try_div_length(a, b)?),
(Length(a), Relative(b)) if b.rel.is_zero() => Float(try_div_length(a, b.abs)?),
(Angle(a), Int(b)) => Angle(a / b as f64),
(Angle(a), Float(b)) => Angle(a / b),
@ -217,9 +217,9 @@ pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> {
(Relative(a), Int(b)) => Relative(a / b as f64),
(Relative(a), Float(b)) => Relative(a / b),
(Relative(a), Length(b)) if a.rel.is_zero() => Float(div_length(a.abs, b)?),
(Relative(a), Length(b)) if a.rel.is_zero() => Float(try_div_length(a.abs, b)?),
(Relative(a), Ratio(b)) if a.abs.is_zero() => Float(a.rel / b),
(Relative(a), Relative(b)) => Float(div_relative(a, b)?),
(Relative(a), Relative(b)) => Float(try_div_relative(a, b)?),
(Fraction(a), Int(b)) => Fraction(a / b as f64),
(Fraction(a), Float(b)) => Fraction(a / b),
@ -230,25 +230,14 @@ pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> {
}
/// Try to divide two lengths.
fn div_length(a: RawLength, b: RawLength) -> StrResult<f64> {
if a.length.is_zero() && b.length.is_zero() {
Ok(a.em / b.em)
} else if a.em.is_zero() && b.em.is_zero() {
Ok(a.length / b.length)
} else {
return Err("cannot divide these two lengths".into());
}
fn try_div_length(a: Length, b: Length) -> StrResult<f64> {
a.try_div(b).ok_or_else(|| "cannot divide these two lengths".into())
}
/// Try to divide two relative lengths.
fn div_relative(a: Relative<RawLength>, b: Relative<RawLength>) -> StrResult<f64> {
if a.rel.is_zero() && b.rel.is_zero() {
div_length(a.abs, b.abs)
} else if a.abs.is_zero() && b.abs.is_zero() {
Ok(a.rel / b.rel)
} else {
return Err("cannot divide these two relative lengths".into());
}
fn try_div_relative(a: Rel<Length>, b: Rel<Length>) -> StrResult<f64> {
a.try_div(b)
.ok_or_else(|| "cannot divide these two relative lengths".into())
}
/// Compute the logical "not" of a value.

View File

@ -5,11 +5,11 @@ use std::sync::Arc;
use comemo::Prehashed;
use super::{Interruption, NodeId, RawLength, Smart, StyleChain};
use crate::geom::{Corners, Length, Numeric, Relative, Sides, Spec};
use super::{Interruption, NodeId, Smart, StyleChain};
use crate::geom::{Abs, Axes, Corners, Em, Length, Numeric, Rel, Sides};
use crate::library::layout::PageNode;
use crate::library::structure::{DescNode, EnumNode, ListNode};
use crate::library::text::ParNode;
use crate::library::text::{ParNode, TextNode};
use crate::util::ReadableTypeId;
/// A style property originating from a set rule or constructor.
@ -163,6 +163,26 @@ pub trait Resolve {
fn resolve(self, styles: StyleChain) -> Self::Output;
}
impl Resolve for Em {
type Output = Abs;
fn resolve(self, styles: StyleChain) -> Self::Output {
if self.is_zero() {
Abs::zero()
} else {
self.at(styles.get(TextNode::SIZE))
}
}
}
impl Resolve for Length {
type Output = Abs;
fn resolve(self, styles: StyleChain) -> Self::Output {
self.abs + self.em.resolve(styles)
}
}
impl<T: Resolve> Resolve for Option<T> {
type Output = Option<T::Output>;
@ -179,8 +199,8 @@ impl<T: Resolve> Resolve for Smart<T> {
}
}
impl<T: Resolve> Resolve for Spec<T> {
type Output = Spec<T::Output>;
impl<T: Resolve> Resolve for Axes<T> {
type Output = Axes<T::Output>;
fn resolve(self, styles: StyleChain) -> Self::Output {
self.map(|v| v.resolve(styles))
@ -203,12 +223,12 @@ impl<T: Resolve> Resolve for Corners<T> {
}
}
impl<T> Resolve for Relative<T>
impl<T> Resolve for Rel<T>
where
T: Resolve + Numeric,
<T as Resolve>::Output: Numeric,
{
type Output = Relative<<T as Resolve>::Output>;
type Output = Rel<<T as Resolve>::Output>;
fn resolve(self, styles: StyleChain) -> Self::Output {
self.map(|abs| abs.resolve(styles))
@ -259,16 +279,16 @@ where
}
}
impl Fold for Sides<Option<Relative<Length>>> {
type Output = Sides<Relative<Length>>;
impl Fold for Sides<Option<Rel<Abs>>> {
type Output = Sides<Rel<Abs>>;
fn fold(self, outer: Self::Output) -> Self::Output {
self.zip(outer, |inner, outer| inner.unwrap_or(outer))
}
}
impl Fold for Sides<Option<Smart<Relative<RawLength>>>> {
type Output = Sides<Smart<Relative<RawLength>>>;
impl Fold for Sides<Option<Smart<Rel<Length>>>> {
type Output = Sides<Smart<Rel<Length>>>;
fn fold(self, outer: Self::Output) -> Self::Output {
self.zip(outer, |inner, outer| inner.unwrap_or(outer))
@ -286,8 +306,8 @@ where
}
}
impl Fold for Corners<Option<Relative<Length>>> {
type Output = Corners<Relative<Length>>;
impl Fold for Corners<Option<Rel<Abs>>> {
type Output = Corners<Rel<Abs>>;
fn fold(self, outer: Self::Output) -> Self::Output {
self.zip(outer, |inner, outer| inner.unwrap_or(outer))

View File

@ -1,11 +1,7 @@
use std::cmp::Ordering;
use std::fmt::{self, Debug, Formatter};
use std::ops::{Add, Div, Mul, Neg};
use super::{Fold, Resolve, Smart, StyleChain, Value};
use crate::geom::{
Align, Em, Get, Length, Numeric, Paint, Relative, Spec, SpecAxis, Stroke,
};
use crate::geom::{Abs, Align, Axes, Axis, Get, Length, Paint, Stroke};
use crate::library::text::TextNode;
/// The unresolved alignment representation.
@ -34,9 +30,9 @@ impl Resolve for RawAlign {
impl RawAlign {
/// The axis this alignment belongs to.
pub const fn axis(self) -> SpecAxis {
pub const fn axis(self) -> Axis {
match self {
Self::Start | Self::End => SpecAxis::Horizontal,
Self::Start | Self::End => Axis::X,
Self::Specific(align) => align.axis(),
}
}
@ -51,8 +47,8 @@ impl From<Align> for RawAlign {
impl Debug for RawAlign {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Start => f.pad("left"),
Self::End => f.pad("center"),
Self::Start => f.pad("start"),
Self::End => f.pad("end"),
Self::Specific(align) => align.fmt(f),
}
}
@ -63,18 +59,18 @@ dynamic! {
}
dynamic! {
Spec<RawAlign>: "2d alignment",
Axes<RawAlign>: "2d alignment",
}
castable! {
Spec<Option<RawAlign>>,
Axes<Option<RawAlign>>,
Expected: "1d or 2d alignment",
@align: RawAlign => {
let mut aligns = Spec::default();
let mut aligns = Axes::default();
aligns.set(align.axis(), Some(*align));
aligns
},
@aligns: Spec<RawAlign> => aligns.map(Some),
@aligns: Axes<RawAlign> => aligns.map(Some),
}
/// The unresolved stroke representation.
@ -83,14 +79,14 @@ castable! {
/// just a paint (`red`), just a thickness (`0.1em`) or both (`2pt + red`) where
/// this is expected.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct RawStroke<T = RawLength> {
pub struct RawStroke<T = Length> {
/// The stroke's paint.
pub paint: Smart<Paint>,
/// The stroke's thickness.
pub thickness: Smart<T>,
}
impl RawStroke<Length> {
impl RawStroke<Abs> {
/// Unpack the stroke, filling missing fields from the `default`.
pub fn unwrap_or(self, default: Stroke) -> Stroke {
Stroke {
@ -106,7 +102,7 @@ impl RawStroke<Length> {
}
impl Resolve for RawStroke {
type Output = RawStroke<Length>;
type Output = RawStroke<Abs>;
fn resolve(self, styles: StyleChain) -> Self::Output {
RawStroke {
@ -116,7 +112,7 @@ impl Resolve for RawStroke {
}
}
impl Fold for RawStroke<Length> {
impl Fold for RawStroke<Abs> {
type Output = Self;
fn fold(self, outer: Self::Output) -> Self::Output {
@ -151,143 +147,3 @@ dynamic! {
thickness: Smart::Auto,
},
}
/// The unresolved length representation.
///
/// Currently supports absolute and em units, but support could quite easily be
/// extended to other units that can be resolved through a style chain.
/// Probably, it would be a good idea to then move to an enum representation
/// that has a small footprint and allocates for the rare case that units are
/// mixed.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct RawLength {
/// The absolute part.
pub length: Length,
/// The font-relative part.
pub em: Em,
}
impl RawLength {
/// The zero length.
pub const fn zero() -> Self {
Self { length: Length::zero(), em: Em::zero() }
}
}
impl Debug for RawLength {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match (self.length.is_zero(), self.em.is_zero()) {
(false, false) => write!(f, "{:?} + {:?}", self.length, self.em),
(true, false) => self.em.fmt(f),
(_, true) => self.length.fmt(f),
}
}
}
impl Resolve for Em {
type Output = Length;
fn resolve(self, styles: StyleChain) -> Self::Output {
if self.is_zero() {
Length::zero()
} else {
self.at(styles.get(TextNode::SIZE))
}
}
}
impl Resolve for RawLength {
type Output = Length;
fn resolve(self, styles: StyleChain) -> Self::Output {
self.length + self.em.resolve(styles)
}
}
impl Numeric for RawLength {
fn zero() -> Self {
Self::zero()
}
fn is_finite(self) -> bool {
self.length.is_finite() && self.em.is_finite()
}
}
impl PartialOrd for RawLength {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.em.is_zero() && other.em.is_zero() {
self.length.partial_cmp(&other.length)
} else if self.length.is_zero() && other.length.is_zero() {
self.em.partial_cmp(&other.em)
} else {
None
}
}
}
impl From<Length> for RawLength {
fn from(length: Length) -> Self {
Self { length, em: Em::zero() }
}
}
impl From<Em> for RawLength {
fn from(em: Em) -> Self {
Self { length: Length::zero(), em }
}
}
impl From<Length> for Relative<RawLength> {
fn from(length: Length) -> Self {
Relative::from(RawLength::from(length))
}
}
impl Neg for RawLength {
type Output = Self;
fn neg(self) -> Self::Output {
Self { length: -self.length, em: -self.em }
}
}
impl Add for RawLength {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
length: self.length + rhs.length,
em: self.em + rhs.em,
}
}
}
sub_impl!(RawLength - RawLength -> RawLength);
impl Mul<f64> for RawLength {
type Output = Self;
fn mul(self, rhs: f64) -> Self::Output {
Self {
length: self.length * rhs,
em: self.em * rhs,
}
}
}
impl Div<f64> for RawLength {
type Output = Self;
fn div(self, rhs: f64) -> Self::Output {
Self {
length: self.length / rhs,
em: self.em / rhs,
}
}
}
assign_impl!(RawLength += RawLength);
assign_impl!(RawLength -= RawLength);
assign_impl!(RawLength *= f64);
assign_impl!(RawLength /= f64);

View File

@ -7,9 +7,9 @@ use std::sync::Arc;
use comemo::Tracked;
use siphasher::sip128::{Hasher128, SipHasher};
use super::{ops, Args, Array, Cast, Content, Dict, Func, Layout, RawLength, Str};
use super::{ops, Args, Array, Cast, Content, Dict, Func, Layout, Str};
use crate::diag::StrResult;
use crate::geom::{Angle, Color, Em, Fraction, Length, Ratio, Relative, RgbaColor};
use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor};
use crate::util::EcoString;
use crate::World;
@ -26,16 +26,16 @@ pub enum Value {
Int(i64),
/// A floating-point number: `1.2`, `10e-4`.
Float(f64),
/// A length: `12pt`, `3cm`, `1.5em`.
Length(RawLength),
/// A length: `12pt`, `3cm`, `1.5em`, `1em - 2pt`.
Length(Length),
/// An angle: `1.5rad`, `90deg`.
Angle(Angle),
/// A ratio: `50%`.
Ratio(Ratio),
/// A relative length, combination of a ratio and a length: `20% + 5cm`.
Relative(Relative<RawLength>),
Relative(Rel<Length>),
/// A fraction: `1fr`.
Fraction(Fraction),
Fraction(Fr),
/// A color value: `#f79143ff`.
Color(Color),
/// A string: `"string"`.
@ -87,11 +87,11 @@ impl Value {
Self::Bool(_) => bool::TYPE_NAME,
Self::Int(_) => i64::TYPE_NAME,
Self::Float(_) => f64::TYPE_NAME,
Self::Length(_) => RawLength::TYPE_NAME,
Self::Length(_) => Length::TYPE_NAME,
Self::Angle(_) => Angle::TYPE_NAME,
Self::Ratio(_) => Ratio::TYPE_NAME,
Self::Relative(_) => Relative::<RawLength>::TYPE_NAME,
Self::Fraction(_) => Fraction::TYPE_NAME,
Self::Relative(_) => Rel::<Length>::TYPE_NAME,
Self::Fraction(_) => Fr::TYPE_NAME,
Self::Color(_) => Color::TYPE_NAME,
Self::Str(_) => Str::TYPE_NAME,
Self::Content(_) => Content::TYPE_NAME,
@ -210,8 +210,8 @@ impl From<usize> for Value {
}
}
impl From<Length> for Value {
fn from(v: Length) -> Self {
impl From<Abs> for Value {
fn from(v: Abs) -> Self {
Self::Length(v.into())
}
}
@ -385,15 +385,15 @@ macro_rules! primitive {
primitive! { bool: "boolean", Bool }
primitive! { i64: "integer", Int }
primitive! { f64: "float", Float, Int(v) => v as f64 }
primitive! { RawLength: "length", Length }
primitive! { Length: "length", Length }
primitive! { Angle: "angle", Angle }
primitive! { Ratio: "ratio", Ratio }
primitive! { Relative<RawLength>: "relative length",
primitive! { Rel<Length>: "relative length",
Relative,
Length(v) => v.into(),
Ratio(v) => v.into()
}
primitive! { Fraction: "fraction", Fraction }
primitive! { Fr: "fraction", Fraction }
primitive! { Color: "color", Color }
primitive! { Str: "string", Str }
primitive! { Content: "content",
@ -422,14 +422,14 @@ mod tests {
test(false, "false");
test(12i64, "12");
test(3.14, "3.14");
test(Length::pt(5.5), "5.5pt");
test(Abs::pt(5.5), "5.5pt");
test(Angle::deg(90.0), "90deg");
test(Ratio::one() / 2.0, "50%");
test(
Ratio::new(0.3) + RawLength::from(Length::cm(2.0)),
Ratio::new(0.3) + Length::from(Abs::cm(2.0)),
"30% + 56.69pt",
);
test(Fraction::one() * 7.55, "7.55fr");
test(Fr::one() * 7.55, "7.55fr");
test(
Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)),
"rgb(\"#010101\")",

View File

@ -1,7 +1,7 @@
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use crate::geom::{AngleUnit, LengthUnit};
use crate::geom::{AbsUnit, AngleUnit};
use crate::util::EcoString;
/// All syntactical building blocks that can be part of a Typst document.
@ -277,7 +277,7 @@ pub struct RawKind {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Unit {
/// An absolute length unit.
Length(LengthUnit),
Length(AbsUnit),
/// An angular unit.
Angle(AngleUnit),
/// Font-relative: `1em` is the same as the font size.

View File

@ -5,7 +5,7 @@ use unscanny::Scanner;
use super::resolve::{resolve_hex, resolve_raw, resolve_string};
use super::{ErrorPos, NodeKind, RawKind, Unit};
use crate::geom::{AngleUnit, LengthUnit};
use crate::geom::{AbsUnit, AngleUnit};
use crate::util::EcoString;
/// An iterator over the tokens of a string of source code.
@ -565,10 +565,10 @@ impl<'s> Tokens<'s> {
match suffix {
"" => NodeKind::Float(v),
"pt" => NodeKind::Numeric(v, Unit::Length(LengthUnit::Pt)),
"mm" => NodeKind::Numeric(v, Unit::Length(LengthUnit::Mm)),
"cm" => NodeKind::Numeric(v, Unit::Length(LengthUnit::Cm)),
"in" => NodeKind::Numeric(v, Unit::Length(LengthUnit::In)),
"pt" => NodeKind::Numeric(v, Unit::Length(AbsUnit::Pt)),
"mm" => NodeKind::Numeric(v, Unit::Length(AbsUnit::Mm)),
"cm" => NodeKind::Numeric(v, Unit::Length(AbsUnit::Cm)),
"in" => NodeKind::Numeric(v, Unit::Length(AbsUnit::In)),
"deg" => NodeKind::Numeric(v, Unit::Angle(AngleUnit::Deg)),
"rad" => NodeKind::Numeric(v, Unit::Angle(AngleUnit::Rad)),
"em" => NodeKind::Numeric(v, Unit::Em),
@ -1089,10 +1089,10 @@ mod tests {
let nums = ints.iter().map(|&(k, v)| (k, v as f64)).chain(floats);
let suffixes: &[(&str, fn(f64) -> NodeKind)] = &[
("mm", |x| Numeric(x, Unit::Length(LengthUnit::Mm))),
("pt", |x| Numeric(x, Unit::Length(LengthUnit::Pt))),
("cm", |x| Numeric(x, Unit::Length(LengthUnit::Cm))),
("in", |x| Numeric(x, Unit::Length(LengthUnit::In))),
("mm", |x| Numeric(x, Unit::Length(AbsUnit::Mm))),
("pt", |x| Numeric(x, Unit::Length(AbsUnit::Pt))),
("cm", |x| Numeric(x, Unit::Length(AbsUnit::Cm))),
("in", |x| Numeric(x, Unit::Length(AbsUnit::In))),
("rad", |x| Numeric(x, Unit::Angle(AngleUnit::Rad))),
("deg", |x| Numeric(x, Unit::Angle(AngleUnit::Deg))),
("em", |x| Numeric(x, Unit::Em)),

View File

@ -17,7 +17,7 @@ use walkdir::WalkDir;
use typst::diag::{FileError, FileResult};
use typst::font::{Font, FontBook};
use typst::frame::{Element, Frame};
use typst::geom::{Length, RgbaColor, Sides};
use typst::geom::{Abs, RgbaColor, Sides};
use typst::library::layout::PageNode;
use typst::library::text::{TextNode, TextSize};
use typst::model::{Smart, StyleMap, Value};
@ -150,13 +150,13 @@ fn config() -> Config {
// exactly 100pt wide. Page height is unbounded and font size is 10pt so
// that it multiplies to nice round numbers.
let mut styles = StyleMap::new();
styles.set(PageNode::WIDTH, Smart::Custom(Length::pt(120.0).into()));
styles.set(PageNode::WIDTH, Smart::Custom(Abs::pt(120.0).into()));
styles.set(PageNode::HEIGHT, Smart::Auto);
styles.set(
PageNode::MARGINS,
Sides::splat(Some(Smart::Custom(Length::pt(10.0).into()))),
Sides::splat(Some(Smart::Custom(Abs::pt(10.0).into()))),
);
styles.set(TextNode::SIZE, TextSize(Length::pt(10.0).into()));
styles.set(TextNode::SIZE, TextSize(Abs::pt(10.0).into()));
// Hook up helpers into the global scope.
let mut std = typst::library::scope();
@ -653,7 +653,7 @@ fn render(frames: &[Frame]) -> sk::Pixmap {
let pixmaps: Vec<_> = frames
.iter()
.map(|frame| {
let limit = Length::cm(100.0);
let limit = Abs::cm(100.0);
if frame.width() > limit || frame.height() > limit {
panic!("overlarge frame: {:?}", frame.size());
}