Switch back to custom geometry types, unified with layout primitives 🏞

This commit is contained in:
Laurenz 2020-10-10 22:19:36 +02:00
parent 42500d5ed8
commit 92c01da360
45 changed files with 1660 additions and 1346 deletions

View File

@ -24,7 +24,6 @@ lto = true
[dependencies]
async-trait = "0.1"
fontdock = { path = "../fontdock", default-features = false }
kurbo = "0.6.3"
tide = { path = "../tide" }
ttf-parser = "0.8.2"
unicode-xid = "0.2"
@ -33,6 +32,7 @@ serde = { version = "1", features = ["derive"], optional = true }
[dev-dependencies]
criterion = "0.3"
futures-executor = "0.3"
kurbo = "0.6.3"
serde_json = "1"
raqote = { version = "0.8", default-features = false }

View File

@ -6,8 +6,7 @@ use fontdock::{FontStretch, FontStyle, FontWeight};
use super::{Value, ValueDict, ValueFunc};
use crate::diag::Diag;
use crate::geom::Linear;
use crate::layout::{Dir, SpecAlign};
use crate::geom::{Dir, Length, Linear, Relative};
use crate::paper::Paper;
use crate::syntax::{Ident, SpanWith, Spanned, SynTree};
@ -37,26 +36,53 @@ impl<T: Convert> Convert for Spanned<T> {
}
}
/// A value type that matches [length] values.
///
/// [length]: enum.Value.html#variant.Length
pub struct Absolute(pub f64);
impl From<Absolute> for f64 {
fn from(abs: Absolute) -> f64 {
abs.0
}
macro_rules! convert_match {
($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
impl $crate::eval::Convert for $type {
fn convert(
value: $crate::syntax::Spanned<$crate::eval::Value>
) -> (Result<Self, $crate::eval::Value>, Option<$crate::diag::Diag>) {
#[allow(unreachable_patterns)]
match value.v {
$($p => (Ok($r), None)),*,
v => {
let err = $crate::error!("expected {}, found {}", $name, v.ty());
(Err(v), Some(err))
},
}
}
}
};
}
/// A value type that matches [relative] values.
///
/// [relative]: enum.Value.html#variant.Relative
pub struct Relative(pub f64);
impl From<Relative> for f64 {
fn from(rel: Relative) -> f64 {
rel.0
}
macro_rules! convert_ident {
($type:ty, $name:expr, $parse:expr) => {
impl $crate::eval::Convert for $type {
fn convert(
value: $crate::syntax::Spanned<$crate::eval::Value>,
) -> (
Result<Self, $crate::eval::Value>,
Option<$crate::diag::Diag>,
) {
match value.v {
Value::Ident(id) => {
if let Some(thing) = $parse(&id) {
(Ok(thing), None)
} else {
(
Err($crate::eval::Value::Ident(id)),
Some($crate::error!("invalid {}", $name)),
)
}
}
v => {
let err = $crate::error!("expected {}, found {}", $name, v.ty());
(Err(v), Some(err))
}
}
}
}
};
}
/// A value type that matches [identifier] and [string] values.
@ -79,70 +105,31 @@ impl Deref for StringLike {
}
}
macro_rules! impl_match {
($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
impl Convert for $type {
fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>) {
#[allow(unreachable_patterns)]
match value.v {
$($p => (Ok($r), None)),*,
v => {
let err = error!("expected {}, found {}", $name, v.ty());
(Err(v), Some(err))
},
}
}
}
};
}
impl_match!(Value, "value", v => v);
impl_match!(Ident, "identifier", Value::Ident(v) => v);
impl_match!(bool, "bool", Value::Bool(v) => v);
impl_match!(i64, "integer", Value::Int(v) => v);
impl_match!(f64, "float",
convert_match!(Value, "value", v => v);
convert_match!(Ident, "identifier", Value::Ident(v) => v);
convert_match!(bool, "bool", Value::Bool(v) => v);
convert_match!(i64, "integer", Value::Int(v) => v);
convert_match!(f64, "float",
Value::Int(v) => v as f64,
Value::Float(v) => v,
);
impl_match!(Absolute, "length", Value::Length(v) => Absolute(v));
impl_match!(Relative, "relative", Value::Relative(v) => Relative(v));
impl_match!(Linear, "linear",
convert_match!(Length, "length", Value::Length(v) => v);
convert_match!(Relative, "relative", Value::Relative(v) => v);
convert_match!(Linear, "linear",
Value::Linear(v) => v,
Value::Length(v) => Linear::abs(v),
Value::Relative(v) => Linear::rel(v),
Value::Length(v) => v.into(),
Value::Relative(v) => v.into(),
);
impl_match!(String, "string", Value::Str(v) => v);
impl_match!(SynTree, "tree", Value::Content(v) => v);
impl_match!(ValueDict, "dictionary", Value::Dict(v) => v);
impl_match!(ValueFunc, "function", Value::Func(v) => v);
impl_match!(StringLike, "identifier or string",
convert_match!(String, "string", Value::Str(v) => v);
convert_match!(SynTree, "tree", Value::Content(v) => v);
convert_match!(ValueDict, "dictionary", Value::Dict(v) => v);
convert_match!(ValueFunc, "function", Value::Func(v) => v);
convert_match!(StringLike, "identifier or string",
Value::Ident(Ident(v)) => StringLike(v),
Value::Str(v) => StringLike(v),
);
macro_rules! impl_ident {
($type:ty, $name:expr, $parse:expr) => {
impl Convert for $type {
fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>) {
match value.v {
Value::Ident(id) => {
if let Some(thing) = $parse(&id) {
(Ok(thing), None)
} else {
(Err(Value::Ident(id)), Some(error!("invalid {}", $name)))
}
}
v => {
let err = error!("expected {}, found {}", $name, v.ty());
(Err(v), Some(err))
}
}
}
}
};
}
impl_ident!(Dir, "direction", |v| match v {
convert_ident!(Dir, "direction", |v| match v {
"ltr" => Some(Self::LTR),
"rtl" => Some(Self::RTL),
"ttb" => Some(Self::TTB),
@ -150,18 +137,9 @@ impl_ident!(Dir, "direction", |v| match v {
_ => None,
});
impl_ident!(SpecAlign, "alignment", |v| match v {
"left" => Some(Self::Left),
"right" => Some(Self::Right),
"top" => Some(Self::Top),
"bottom" => Some(Self::Bottom),
"center" => Some(Self::Center),
_ => None,
});
impl_ident!(FontStyle, "font style", Self::from_str);
impl_ident!(FontStretch, "font stretch", Self::from_str);
impl_ident!(Paper, "paper", Self::from_name);
convert_ident!(FontStyle, "font style", Self::from_str);
convert_ident!(FontStretch, "font stretch", Self::from_str);
convert_ident!(Paper, "paper", Self::from_name);
impl Convert for FontWeight {
fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>) {

View File

@ -1,7 +1,8 @@
//! Evaluation of syntax trees.
mod args;
#[macro_use]
mod convert;
mod args;
mod dict;
mod scope;
mod state;
@ -22,10 +23,10 @@ use fontdock::FontStyle;
use crate::diag::Diag;
use crate::diag::{Deco, Feedback, Pass};
use crate::layout::nodes::{
use crate::geom::{Gen, Length, Relative, Spec, Switch};
use crate::layout::{
Document, LayoutNode, Pad, Pages, Par, Softness, Spacing, Stack, Text,
};
use crate::layout::{Gen2, Spec2, Switch};
use crate::syntax::*;
/// Evaluate a syntax tree into a document.
@ -168,7 +169,7 @@ impl EvalContext {
dirs,
children,
aligns,
expand: Spec2::new(true, true),
expand: Spec::new(true, true),
}),
}),
})
@ -195,7 +196,7 @@ impl EvalContext {
line_spacing,
children,
aligns,
expand: Gen2::new(false, expand_cross).switch(dirs),
expand: Gen::new(false, expand_cross).switch(dirs),
});
}
}
@ -337,7 +338,7 @@ impl Eval for NodeRaw {
dirs: ctx.state.dirs,
children,
aligns: ctx.state.aligns,
expand: Spec2::new(false, false),
expand: Spec::new(false, false),
});
ctx.state.text.fallback = prev;
@ -366,8 +367,8 @@ impl Eval for Lit {
Lit::Bool(v) => Value::Bool(v),
Lit::Int(v) => Value::Int(v),
Lit::Float(v) => Value::Float(v),
Lit::Length(v) => Value::Length(v.as_raw()),
Lit::Percent(v) => Value::Relative(v / 100.0),
Lit::Length(v, unit) => Value::Length(Length::with_unit(v, unit)),
Lit::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
Lit::Color(v) => Value::Color(v),
Lit::Str(ref v) => Value::Str(v.clone()),
Lit::Dict(ref v) => Value::Dict(v.eval(ctx)),
@ -473,7 +474,6 @@ fn neg(ctx: &mut EvalContext, span: Span, value: Value) -> Value {
/// Compute the sum of two values.
fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
use crate::geom::Linear as Lin;
use Value::*;
match (lhs, rhs) {
// Numbers to themselves.
@ -484,15 +484,15 @@ fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
// Lengths, relatives and linears to themselves.
(Length(a), Length(b)) => Length(a + b),
(Length(a), Relative(b)) => Linear(Lin::abs(a) + Lin::rel(b)),
(Length(a), Linear(b)) => Linear(Lin::abs(a) + b),
(Length(a), Relative(b)) => Linear(a + b),
(Length(a), Linear(b)) => Linear(a + b),
(Relative(a), Length(b)) => Linear(Lin::rel(a) + Lin::abs(b)),
(Relative(a), Length(b)) => Linear(a + b),
(Relative(a), Relative(b)) => Relative(a + b),
(Relative(a), Linear(b)) => Linear(Lin::rel(a) + b),
(Relative(a), Linear(b)) => Linear(a + b),
(Linear(a), Length(b)) => Linear(a + Lin::abs(b)),
(Linear(a), Relative(b)) => Linear(a + Lin::rel(b)),
(Linear(a), Length(b)) => Linear(a + b),
(Linear(a), Relative(b)) => Linear(a + b),
(Linear(a), Linear(b)) => Linear(a + b),
// Complex data types to themselves.
@ -509,7 +509,6 @@ fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
/// Compute the difference of two values.
fn sub(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
use crate::geom::Linear as Lin;
use Value::*;
match (lhs, rhs) {
// Numbers from themselves.
@ -520,13 +519,13 @@ fn sub(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
// Lengths, relatives and linears from themselves.
(Length(a), Length(b)) => Length(a - b),
(Length(a), Relative(b)) => Linear(Lin::abs(a) - Lin::rel(b)),
(Length(a), Linear(b)) => Linear(Lin::abs(a) - b),
(Relative(a), Length(b)) => Linear(Lin::rel(a) - Lin::abs(b)),
(Length(a), Relative(b)) => Linear(a - b),
(Length(a), Linear(b)) => Linear(a - b),
(Relative(a), Length(b)) => Linear(a - b),
(Relative(a), Relative(b)) => Relative(a - b),
(Relative(a), Linear(b)) => Linear(Lin::rel(a) - b),
(Linear(a), Length(b)) => Linear(a - Lin::abs(b)),
(Linear(a), Relative(b)) => Linear(a - Lin::rel(b)),
(Relative(a), Linear(b)) => Linear(a - b),
(Linear(a), Length(b)) => Linear(a - b),
(Linear(a), Relative(b)) => Linear(a - b),
(Linear(a), Linear(b)) => Linear(a - b),
(a, b) => {
@ -561,8 +560,8 @@ fn mul(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
(Float(a), Linear(b)) => Linear(a * b),
// Integers with strings.
(Int(a), Str(b)) => Str(b.repeat(a.max(0) as usize)),
(Str(a), Int(b)) => Str(a.repeat(b.max(0) as usize)),
(Int(a), Str(b)) => Str(b.repeat(0.max(a) as usize)),
(Str(a), Int(b)) => Str(a.repeat(0.max(b) as usize)),
(a, b) => {
ctx.diag(error!(span, "cannot multiply {} with {}", a.ty(), b.ty()));

View File

@ -5,9 +5,7 @@ use std::rc::Rc;
use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight};
use super::Scope;
use crate::geom::{Linear, Size};
use crate::layout::{Dir, Gen2, GenAlign, Sides};
use crate::length::Length;
use crate::geom::{Align, Dir, Gen, Length, Linear, Relative, Sides, Size};
use crate::paper::{Paper, PaperClass, PAPER_A4};
/// The active evaluation state.
@ -20,9 +18,9 @@ pub struct State {
/// The page state.
pub page: PageState,
/// The active layouting directions.
pub dirs: Gen2<Dir>,
pub dirs: Gen<Dir>,
/// The active alignments.
pub aligns: Gen2<GenAlign>,
pub aligns: Gen<Align>,
}
impl Default for State {
@ -31,8 +29,8 @@ impl Default for State {
scope: crate::library::_std(),
text: TextState::default(),
page: PageState::default(),
dirs: Gen2::new(Dir::TTB, Dir::LTR),
aligns: Gen2::new(GenAlign::Start, GenAlign::Start),
dirs: Gen::new(Dir::TTB, Dir::LTR),
aligns: Gen::new(Align::Start, Align::Start),
}
}
}
@ -62,22 +60,22 @@ pub struct TextState {
impl TextState {
/// The absolute font size.
pub fn font_size(&self) -> f64 {
pub fn font_size(&self) -> Length {
self.font_size.eval()
}
/// The absolute word spacing.
pub fn word_spacing(&self) -> f64 {
pub fn word_spacing(&self) -> Length {
self.word_spacing.eval(self.font_size())
}
/// The absolute line spacing.
pub fn line_spacing(&self) -> f64 {
pub fn line_spacing(&self) -> Length {
self.line_spacing.eval(self.font_size())
}
/// The absolute paragraph spacing.
pub fn par_spacing(&self) -> f64 {
pub fn par_spacing(&self) -> Length {
self.par_spacing.eval(self.font_size())
}
}
@ -105,10 +103,10 @@ impl Default for TextState {
},
strong: false,
emph: false,
font_size: FontSize::abs(Length::pt(11.0).as_raw()),
word_spacing: Linear::rel(0.25),
line_spacing: Linear::rel(0.2),
par_spacing: Linear::rel(0.5),
font_size: FontSize::abs(Length::pt(11.0)),
word_spacing: Relative::new(0.25).into(),
line_spacing: Relative::new(0.2).into(),
par_spacing: Relative::new(0.5).into(),
}
}
}
@ -117,7 +115,7 @@ impl Default for TextState {
#[derive(Debug, Clone, PartialEq)]
pub struct FontSize {
/// The base font size, updated whenever the font size is set absolutely.
pub base: f64,
pub base: Length,
/// The scale to apply on the base font size, updated when the font size
/// is set relatively.
pub scale: Linear,
@ -125,17 +123,17 @@ pub struct FontSize {
impl FontSize {
/// Create a new font size.
pub fn new(base: f64, scale: Linear) -> Self {
Self { base, scale }
pub fn new(base: Length, scale: impl Into<Linear>) -> Self {
Self { base, scale: scale.into() }
}
/// Create a new font size with the given `base` and a scale of `1.0`.
pub fn abs(base: f64) -> Self {
Self::new(base, Linear::rel(1.0))
pub fn abs(base: Length) -> Self {
Self::new(base, Relative::ONE)
}
/// Compute the absolute font size.
pub fn eval(&self) -> f64 {
pub fn eval(&self) -> Length {
self.scale.eval(self.base)
}
}

View File

@ -6,7 +6,7 @@ use std::rc::Rc;
use super::{Args, Dict, Eval, EvalContext, SpannedEntry};
use crate::color::RgbaColor;
use crate::geom::Linear;
use crate::geom::{Length, Linear, Relative};
use crate::syntax::{Ident, SynTree};
/// A computational value.
@ -23,14 +23,9 @@ pub enum Value {
/// A floating-point number: `1.2, 200%`.
Float(f64),
/// A length: `2cm, 5.2in`.
Length(f64),
Length(Length),
/// A relative value: `50%`.
///
/// _Note_: `50%` is represented as `0.5` here, but as `50.0` in the
/// corresponding [literal].
///
/// [literal]: ../syntax/ast/enum.Lit.html#variant.Percent
Relative(f64),
Relative(Relative),
/// A combination of an absolute length and a relative value: `20% + 5cm`.
Linear(Linear),
/// A color value with alpha channel: `#f79143ff`.

View File

@ -14,8 +14,8 @@ use tide::{PdfWriter, Rect, Ref, Trailer, Version};
use ttf_parser::{name_id, GlyphId};
use crate::font::FontLoader;
use crate::geom::Length;
use crate::layout::{BoxLayout, LayoutElement};
use crate::length::Length;
/// Export a list of layouts into a _PDF_ document.
///
@ -110,8 +110,8 @@ impl<'a, W: Write> PdfExporter<'a, W> {
let rect = Rect::new(
0.0,
0.0,
Length::raw(page.size.width).as_pt() as f32,
Length::raw(page.size.height).as_pt() as f32,
page.size.width.to_pt() as f32,
page.size.height.to_pt() as f32,
);
self.writer.write_obj(
@ -136,7 +136,7 @@ impl<'a, W: Write> PdfExporter<'a, W> {
// Font switching actions are only written when the face used for
// shaped text changes. Hence, we need to remember the active face.
let mut face = FaceId::MAX;
let mut size = 0.0;
let mut size = Length::ZERO;
for (pos, element) in &page.elements {
match element {
@ -147,12 +147,12 @@ impl<'a, W: Write> PdfExporter<'a, W> {
size = shaped.size;
text.tf(
self.to_pdf[&shaped.face] as u32 + 1,
Length::raw(size).as_pt() as f32,
size.to_pt() as f32,
);
}
let x = Length::raw(pos.x).as_pt();
let y = Length::raw(page.size.height - pos.y - size).as_pt();
let x = pos.x.to_pt();
let y = (page.size.height - pos.y - size).to_pt();
text.tm(1.0, 0.0, 0.0, 1.0, x as f32, y as f32);
text.tj(shaped.encode_glyphs_be());
}

View File

@ -1,235 +0,0 @@
//! Geometrical types.
#[doc(no_inline)]
pub use kurbo::*;
use std::fmt::{self, Debug, Formatter};
use std::ops::*;
use crate::layout::{Dir, Gen2, GenAlign, Get, Side, Spec2, SpecAxis, Switch};
macro_rules! impl_2d {
($t:ty, $x:ident, $y:ident) => {
impl Get<SpecAxis> for $t {
type Component = f64;
fn get(self, axis: SpecAxis) -> f64 {
match axis {
SpecAxis::Horizontal => self.$x,
SpecAxis::Vertical => self.$y,
}
}
fn get_mut(&mut self, axis: SpecAxis) -> &mut f64 {
match axis {
SpecAxis::Horizontal => &mut self.$x,
SpecAxis::Vertical => &mut self.$y,
}
}
}
impl Switch for $t {
type Other = Gen2<f64>;
fn switch(self, dirs: Gen2<Dir>) -> Self::Other {
Spec2::new(self.$x, self.$y).switch(dirs)
}
}
};
}
impl_2d!(Point, x, y);
impl_2d!(Vec2, x, y);
impl_2d!(Size, width, height);
impl Get<Side> for Rect {
type Component = f64;
fn get(self, side: Side) -> f64 {
match side {
Side::Left => self.x0,
Side::Top => self.y0,
Side::Right => self.x1,
Side::Bottom => self.y1,
}
}
fn get_mut(&mut self, side: Side) -> &mut f64 {
match side {
Side::Left => &mut self.x0,
Side::Top => &mut self.y0,
Side::Right => &mut self.x1,
Side::Bottom => &mut self.y1,
}
}
}
/// Additional methods for [sizes].
///
/// [sizes]: ../../kurbo/struct.Size.html
pub trait SizeExt {
/// Whether the given size fits into this one, that is, both coordinate
/// values are smaller or equal.
fn fits(self, other: Self) -> bool;
/// The anchor position for an object to be aligned in a container with this
/// size and the given directions.
fn anchor(self, dirs: Gen2<Dir>, aligns: Gen2<GenAlign>) -> Point;
}
impl SizeExt for Size {
fn fits(self, other: Self) -> bool {
self.width >= other.width && self.height >= other.height
}
fn anchor(self, dirs: Gen2<Dir>, aligns: Gen2<GenAlign>) -> Point {
fn anchor(length: f64, dir: Dir, align: GenAlign) -> f64 {
match if dir.is_positive() { align } else { align.inv() } {
GenAlign::Start => 0.0,
GenAlign::Center => length / 2.0,
GenAlign::End => length,
}
}
let switched = self.switch(dirs);
let generic = Gen2::new(
anchor(switched.main, dirs.main, aligns.main),
anchor(switched.cross, dirs.cross, aligns.cross),
);
generic.switch(dirs).to_point()
}
}
/// A function that depends linearly on one value.
///
/// This represents a function `f(x) = rel * x + abs`.
#[derive(Copy, Clone, PartialEq)]
pub struct Linear {
/// The relative part.
pub rel: f64,
/// The absolute part.
pub abs: f64,
}
impl Linear {
/// The constant zero function.
pub const ZERO: Linear = Linear { rel: 0.0, abs: 0.0 };
/// Create a new linear function.
pub fn new(rel: f64, abs: f64) -> Self {
Self { rel, abs }
}
/// Create a new linear function with only a relative component.
pub fn rel(rel: f64) -> Self {
Self { rel, abs: 0.0 }
}
/// Create a new linear function with only an absolute component.
pub fn abs(abs: f64) -> Self {
Self { rel: 0.0, abs }
}
/// Evaluate the linear function with the given value.
pub fn eval(self, x: f64) -> f64 {
self.rel * x + self.abs
}
}
impl Add for Linear {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
rel: self.rel + other.rel,
abs: self.abs + other.abs,
}
}
}
impl AddAssign for Linear {
fn add_assign(&mut self, other: Self) {
self.rel += other.rel;
self.abs += other.abs;
}
}
impl Sub for Linear {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self {
rel: self.rel - other.rel,
abs: self.abs - other.abs,
}
}
}
impl SubAssign for Linear {
fn sub_assign(&mut self, other: Self) {
self.rel -= other.rel;
self.abs -= other.abs;
}
}
impl Mul<f64> for Linear {
type Output = Self;
fn mul(self, other: f64) -> Self {
Self {
rel: self.rel * other,
abs: self.abs * other,
}
}
}
impl MulAssign<f64> for Linear {
fn mul_assign(&mut self, other: f64) {
self.rel *= other;
self.abs *= other;
}
}
impl Mul<Linear> for f64 {
type Output = Linear;
fn mul(self, other: Linear) -> Linear {
Linear {
rel: self * other.rel,
abs: self * other.abs,
}
}
}
impl Div<f64> for Linear {
type Output = Self;
fn div(self, other: f64) -> Self {
Self {
rel: self.rel / other,
abs: self.abs / other,
}
}
}
impl DivAssign<f64> for Linear {
fn div_assign(&mut self, other: f64) {
self.rel /= other;
self.abs /= other;
}
}
impl Neg for Linear {
type Output = Self;
fn neg(self) -> Self {
Self { rel: -self.rel, abs: -self.abs }
}
}
impl Debug for Linear {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}x + {}", self.rel, self.abs)
}
}

48
src/geom/align.rs Normal file
View File

@ -0,0 +1,48 @@
use super::*;
/// Where to align something along a directed axis.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum Align {
/// Align at the start of the axis.
Start,
/// Align in the middle of the axis.
Center,
/// Align at the end of the axis.
End,
}
impl Align {
/// Returns the position of this alignment in the given range.
pub fn apply(self, range: Range<Length>) -> Length {
match self {
Self::Start => range.start,
Self::Center => (range.start + range.end) / 2.0,
Self::End => range.end,
}
}
/// The inverse alignment.
pub fn inv(self) -> Self {
match self {
Self::Start => Self::End,
Self::Center => Self::Center,
Self::End => Self::Start,
}
}
}
impl Default for Align {
fn default() -> Self {
Self::Start
}
}
impl Display for Align {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::Start => "start",
Self::Center => "center",
Self::End => "end",
})
}
}

83
src/geom/dir.rs Normal file
View File

@ -0,0 +1,83 @@
use super::*;
/// The four directions into which content can be laid out.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Dir {
/// Left to right.
LTR,
/// Right to left.
RTL,
/// Top to bottom.
TTB,
/// Bottom to top.
BTT,
}
impl Dir {
/// The side this direction starts at.
pub fn start(self) -> Side {
match self {
Self::LTR => Side::Left,
Self::RTL => Side::Right,
Self::TTB => Side::Top,
Self::BTT => Side::Bottom,
}
}
/// The side this direction ends at.
pub fn end(self) -> Side {
match self {
Self::LTR => Side::Right,
Self::RTL => Side::Left,
Self::TTB => Side::Bottom,
Self::BTT => Side::Top,
}
}
/// The specific axis this direction belongs to.
pub fn axis(self) -> SpecAxis {
match self {
Self::LTR | Self::RTL => SpecAxis::Horizontal,
Self::TTB | Self::BTT => SpecAxis::Vertical,
}
}
/// Whether this direction points into the positive coordinate direction.
///
/// The positive directions are left-to-right and top-to-bottom.
pub fn is_positive(self) -> bool {
match self {
Self::LTR | Self::TTB => true,
Self::RTL | Self::BTT => false,
}
}
/// The factor for this direction.
///
/// - `1.0` if the direction is positive.
/// - `-1.0` if the direction is negative.
pub fn factor(self) -> f64 {
if self.is_positive() { 1.0 } else { -1.0 }
}
/// The inverse direction.
pub fn inv(self) -> Self {
match self {
Self::LTR => Self::RTL,
Self::RTL => Self::LTR,
Self::TTB => Self::BTT,
Self::BTT => Self::TTB,
}
}
}
impl Display for Dir {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::LTR => "ltr",
Self::RTL => "rtl",
Self::TTB => "ttb",
Self::BTT => "btt",
})
}
}

90
src/geom/gen.rs Normal file
View File

@ -0,0 +1,90 @@
use super::*;
/// A container with a main and cross component.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct Gen<T> {
/// The main component.
pub main: T,
/// The cross component.
pub cross: T,
}
impl<T> Gen<T> {
/// Create a new instance from the two components.
pub fn new(main: T, cross: T) -> Self {
Self { main, cross }
}
}
impl Gen<Length> {
/// The zero value.
pub const ZERO: Self = Self { main: Length::ZERO, cross: Length::ZERO };
}
impl<T> Get<GenAxis> for Gen<T> {
type Component = T;
fn get(self, axis: GenAxis) -> T {
match axis {
GenAxis::Main => self.main,
GenAxis::Cross => self.cross,
}
}
fn get_mut(&mut self, axis: GenAxis) -> &mut T {
match axis {
GenAxis::Main => &mut self.main,
GenAxis::Cross => &mut self.cross,
}
}
}
impl<T> Switch for Gen<T> {
type Other = Spec<T>;
fn switch(self, dirs: Gen<Dir>) -> Self::Other {
match dirs.main.axis() {
SpecAxis::Horizontal => Spec::new(self.main, self.cross),
SpecAxis::Vertical => Spec::new(self.cross, self.main),
}
}
}
/// The two generic layouting axes.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum GenAxis {
/// The axis pages and paragraphs are set along.
Main,
/// The axis words and lines are set along.
Cross,
}
impl GenAxis {
/// The other axis.
pub fn other(self) -> Self {
match self {
Self::Main => Self::Cross,
Self::Cross => Self::Main,
}
}
}
impl Switch for GenAxis {
type Other = SpecAxis;
fn switch(self, dirs: Gen<Dir>) -> Self::Other {
match self {
Self::Main => dirs.main.axis(),
Self::Cross => dirs.cross.axis(),
}
}
}
impl Display for GenAxis {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::Main => "main",
Self::Cross => "cross",
})
}
}

213
src/geom/length.rs Normal file
View File

@ -0,0 +1,213 @@
use super::*;
/// An absolute length.
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
pub struct Length {
/// The length in raw units.
raw: f64,
}
impl Length {
/// The zero length.
pub const ZERO: Self = Self { raw: 0.0 };
/// Create a length from a number of points.
pub fn pt(pt: f64) -> Self {
Self::with_unit(pt, Unit::Pt)
}
/// Create a length from a number of millimeters.
pub fn mm(mm: f64) -> Self {
Self::with_unit(mm, Unit::Mm)
}
/// Create a length from a number of centimeters.
pub fn cm(cm: f64) -> Self {
Self::with_unit(cm, Unit::Cm)
}
/// Create a length from a number of inches.
pub fn inches(inches: f64) -> Self {
Self::with_unit(inches, Unit::In)
}
/// Create a length from a number of raw units.
pub fn raw(raw: f64) -> Self {
Self { raw }
}
/// Convert this to a number of points.
pub fn to_pt(self) -> f64 {
self.to_unit(Unit::Pt)
}
/// Convert this to a number of millimeters.
pub fn to_mm(self) -> f64 {
self.to_unit(Unit::Mm)
}
/// Convert this to a number of centimeters.
pub fn to_cm(self) -> f64 {
self.to_unit(Unit::Cm)
}
/// Convert this to a number of inches.
pub fn to_inches(self) -> f64 {
self.to_unit(Unit::In)
}
/// Get the value of this length in raw units.
pub fn to_raw(self) -> f64 {
self.raw
}
/// Create a length from a value in a unit.
pub fn with_unit(val: f64, unit: Unit) -> Self {
Self { raw: val * unit.raw_scale() }
}
/// Get the value of this length in unit.
pub fn to_unit(self, unit: Unit) -> f64 {
self.raw / unit.raw_scale()
}
/// The minimum of this and another length.
pub fn min(self, other: Self) -> Self {
Self { raw: self.raw.min(other.raw) }
}
/// The maximum of this and another length.
pub fn max(self, other: Self) -> Self {
Self { raw: self.raw.max(other.raw) }
}
}
impl Display for Length {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
// Format small lengths as points and large ones as centimeters.
let (val, unit) = if self.to_pt().abs() < 25.0 {
(self.to_pt(), Unit::Pt)
} else {
(self.to_cm(), Unit::Cm)
};
write!(f, "{:.2}{}", val, unit)
}
}
impl Debug for Length {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
impl Neg for Length {
type Output = Self;
fn neg(self) -> Self {
Self { raw: -self.raw }
}
}
impl Add for Length {
type Output = Self;
fn add(self, other: Self) -> Self {
Self { raw: self.raw + other.raw }
}
}
sub_impl!(Length - Length -> Length);
impl Mul<f64> for Length {
type Output = Self;
fn mul(self, other: f64) -> Self {
Self { raw: self.raw * other }
}
}
impl Mul<Length> for f64 {
type Output = Length;
fn mul(self, other: Length) -> Length {
other * self
}
}
impl Div<f64> for Length {
type Output = Self;
fn div(self, other: f64) -> Self {
Self { raw: self.raw / other }
}
}
assign_impl!(Length += Length);
assign_impl!(Length -= Length);
assign_impl!(Length *= f64);
assign_impl!(Length /= f64);
impl Sum for Length {
fn sum<I: Iterator<Item = Length>>(iter: I) -> Self {
iter.fold(Length::ZERO, Add::add)
}
}
/// Different units of measurement.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum Unit {
/// Points.
Pt,
/// Millimeters.
Mm,
/// Centimeters.
Cm,
/// Inches.
In,
}
impl Unit {
/// How many raw units correspond to a value of `1.0` in this unit.
fn raw_scale(self) -> f64 {
match self {
Unit::Pt => 1.0,
Unit::Mm => 2.83465,
Unit::Cm => 28.3465,
Unit::In => 72.0,
}
}
}
impl Display for Unit {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Unit::Mm => "mm",
Unit::Pt => "pt",
Unit::Cm => "cm",
Unit::In => "in",
})
}
}
impl Debug for Unit {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_length_formats_correctly() {
assert_eq!(Length::pt(-28.34).to_string(), "-1.00cm".to_string());
assert_eq!(Length::pt(23.0).to_string(), "23.00pt".to_string());
assert_eq!(Length::cm(12.728).to_string(), "12.73cm".to_string());
}
#[test]
fn test_length_unit_conversion() {
assert!((Length::mm(150.0).to_cm() - 15.0) < 1e-4);
}
}

172
src/geom/linear.rs Normal file
View File

@ -0,0 +1,172 @@
use super::*;
/// A combined relative and absolute length.
#[derive(Default, Copy, Clone, PartialEq)]
pub struct Linear {
/// The relative part.
pub rel: Relative,
/// The absolute part.
pub abs: Length,
}
impl Linear {
/// The zero linear.
pub const ZERO: Linear = Linear { rel: Relative::ZERO, abs: Length::ZERO };
/// Create a new linear.
pub fn new(rel: Relative, abs: Length) -> Self {
Self { rel, abs }
}
/// Evaluate the linear length with `one` being `100%` for the relative
/// part.
pub fn eval(self, one: Length) -> Length {
self.rel.eval(one) + self.abs
}
/// Whether this linear's relative component is zero.
pub fn is_absolute(self) -> bool {
self.rel == Relative::ZERO
}
}
impl Display for Linear {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{} + {}", self.rel, self.abs)
}
}
impl Debug for Linear {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
impl From<Length> for Linear {
fn from(abs: Length) -> Self {
Self { rel: Relative::ZERO, abs }
}
}
impl From<Relative> for Linear {
fn from(rel: Relative) -> Self {
Self { rel, abs: Length::ZERO }
}
}
impl Neg for Linear {
type Output = Self;
fn neg(self) -> Self {
Self { rel: -self.rel, abs: -self.abs }
}
}
impl Add for Linear {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
rel: self.rel + other.rel,
abs: self.abs + other.abs,
}
}
}
impl Add<Relative> for Length {
type Output = Linear;
fn add(self, other: Relative) -> Linear {
Linear { rel: other, abs: self }
}
}
impl Add<Length> for Relative {
type Output = Linear;
fn add(self, other: Length) -> Linear {
other + self
}
}
impl Add<Length> for Linear {
type Output = Self;
fn add(self, other: Length) -> Self {
Self { rel: self.rel, abs: self.abs + other }
}
}
impl Add<Linear> for Length {
type Output = Linear;
fn add(self, other: Linear) -> Linear {
other + self
}
}
impl Add<Relative> for Linear {
type Output = Self;
fn add(self, other: Relative) -> Self {
Self { rel: self.rel + other, abs: self.abs }
}
}
impl Add<Linear> for Relative {
type Output = Linear;
fn add(self, other: Linear) -> Linear {
other + self
}
}
sub_impl!(Linear - Linear -> Linear);
sub_impl!(Length - Relative -> Linear);
sub_impl!(Relative - Length -> Linear);
sub_impl!(Linear - Length -> Linear);
sub_impl!(Length - Linear -> Linear);
sub_impl!(Linear - Relative -> Linear);
sub_impl!(Relative - Linear -> Linear);
impl Mul<f64> for Linear {
type Output = Self;
fn mul(self, other: f64) -> Self {
Self {
rel: self.rel * other,
abs: self.abs * other,
}
}
}
impl Mul<Linear> for f64 {
type Output = Linear;
fn mul(self, other: Linear) -> Linear {
Linear {
rel: self * other.rel,
abs: self * other.abs,
}
}
}
impl Div<f64> for Linear {
type Output = Self;
fn div(self, other: f64) -> Self {
Self {
rel: self.rel / other,
abs: self.abs / other,
}
}
}
assign_impl!(Linear += Linear);
assign_impl!(Linear += Length);
assign_impl!(Linear += Relative);
assign_impl!(Linear -= Linear);
assign_impl!(Linear -= Length);
assign_impl!(Linear -= Relative);
assign_impl!(Linear *= f64);
assign_impl!(Linear /= f64);

47
src/geom/macros.rs Normal file
View File

@ -0,0 +1,47 @@
/// Implement the `Sub` trait based on existing `Neg` and `Add` impls.
macro_rules! sub_impl {
($a:ident - $b:ident -> $c:ident) => {
impl Sub<$b> for $a {
type Output = $c;
fn sub(self, other: $b) -> $c {
self + -other
}
}
};
}
/// Implement an assign trait based on an existing non-assign trait.
macro_rules! assign_impl {
($a:ident += $b:ident) => {
impl AddAssign<$b> for $a {
fn add_assign(&mut self, other: $b) {
*self = *self + other;
}
}
};
($a:ident -= $b:ident) => {
impl SubAssign<$b> for $a {
fn sub_assign(&mut self, other: $b) {
*self = *self - other;
}
}
};
($a:ident *= $b:ident) => {
impl MulAssign<$b> for $a {
fn mul_assign(&mut self, other: $b) {
*self = *self * other;
}
}
};
($a:ident /= $b:ident) => {
impl DivAssign<$b> for $a {
fn div_assign(&mut self, other: $b) {
*self = *self / other;
}
}
};
}

53
src/geom/mod.rs Normal file
View File

@ -0,0 +1,53 @@
//! Geometrical primitivies.
#[macro_use]
mod macros;
mod align;
mod dir;
mod gen;
mod length;
mod linear;
mod point;
mod relative;
mod sides;
mod size;
mod spec;
pub use align::*;
pub use dir::*;
pub use gen::*;
pub use length::*;
pub use linear::*;
pub use point::*;
pub use relative::*;
pub use sides::*;
pub use size::*;
pub use spec::*;
use std::fmt::{self, Debug, Display, Formatter};
use std::iter::Sum;
use std::ops::*;
/// Generic access to a structure's components.
pub trait Get<Index> {
/// The structure's component type.
type Component;
/// Return the component for the specified index.
fn get(self, index: Index) -> Self::Component;
/// Borrow the component for the specified index mutably.
fn get_mut(&mut self, index: Index) -> &mut Self::Component;
}
/// Switch between the specific and generic representations of a type.
///
/// The generic representation deals with main and cross axes while the specific
/// representation deals with horizontal and vertical axes.
pub trait Switch {
/// The type of the other version.
type Other;
/// The other version of this type based on the current directions.
fn switch(self, dirs: Gen<Dir>) -> Self::Other;
}

102
src/geom/point.rs Normal file
View File

@ -0,0 +1,102 @@
use super::*;
/// A point in 2D.
#[derive(Default, Copy, Clone, PartialEq)]
pub struct Point {
/// The x coordinate.
pub x: Length,
/// The y coordinate.
pub y: Length,
}
impl Point {
/// The origin point.
pub const ZERO: Self = Self { x: Length::ZERO, y: Length::ZERO };
/// Create a new point from x and y coordinate.
pub fn new(x: Length, y: Length) -> Self {
Self { x, y }
}
}
impl Get<SpecAxis> for Point {
type Component = Length;
fn get(self, axis: SpecAxis) -> Length {
match axis {
SpecAxis::Horizontal => self.x,
SpecAxis::Vertical => self.y,
}
}
fn get_mut(&mut self, axis: SpecAxis) -> &mut Length {
match axis {
SpecAxis::Horizontal => &mut self.x,
SpecAxis::Vertical => &mut self.y,
}
}
}
impl Switch for Point {
type Other = Gen<Length>;
fn switch(self, dirs: Gen<Dir>) -> Self::Other {
match dirs.main.axis() {
SpecAxis::Horizontal => Gen::new(self.x, self.y),
SpecAxis::Vertical => Gen::new(self.y, self.x),
}
}
}
impl Debug for Point {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
impl Neg for Point {
type Output = Self;
fn neg(self) -> Self {
Self { x: -self.x, y: -self.y }
}
}
impl Add for Point {
type Output = Self;
fn add(self, other: Self) -> Self {
Self { x: self.x + other.x, y: self.y + other.y }
}
}
sub_impl!(Point - Point -> Point);
impl Mul<f64> for Point {
type Output = Self;
fn mul(self, other: f64) -> Self {
Self { x: self.x * other, y: self.y * other }
}
}
impl Mul<Point> for f64 {
type Output = Point;
fn mul(self, other: Point) -> Point {
other * self
}
}
impl Div<f64> for Point {
type Output = Self;
fn div(self, other: f64) -> Self {
Self { x: self.x / other, y: self.y / other }
}
}
assign_impl!(Point += Point);
assign_impl!(Point -= Point);
assign_impl!(Point *= f64);
assign_impl!(Point /= f64);

92
src/geom/relative.rs Normal file
View File

@ -0,0 +1,92 @@
use super::*;
/// A relative length.
///
/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the
/// corresponding [literal].
///
/// [literal]: ../syntax/ast/enum.Lit.html#variant.Percent
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
pub struct Relative(f64);
impl Relative {
/// A ratio of `0%` represented as `0.0`.
pub const ZERO: Self = Self(0.0);
/// A ratio of `100%` represented as `1.0`.
pub const ONE: Self = Self(1.0);
/// Create a new relative value.
pub fn new(ratio: f64) -> Self {
Self(ratio)
}
/// Get the underlying ratio.
pub fn get(self) -> f64 {
self.0
}
/// Evaluate the relative length with `one` being `100%`.
pub fn eval(self, one: Length) -> Length {
self.get() * one
}
}
impl Display for Relative {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:.2}%", self.0)
}
}
impl Debug for Relative {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
impl Neg for Relative {
type Output = Self;
fn neg(self) -> Self {
Self(-self.0)
}
}
impl Add for Relative {
type Output = Self;
fn add(self, other: Self) -> Self {
Self(self.0 + other.0)
}
}
sub_impl!(Relative - Relative -> Relative);
impl Mul<f64> for Relative {
type Output = Self;
fn mul(self, other: f64) -> Self {
Self(self.0 * other)
}
}
impl Mul<Relative> for f64 {
type Output = Relative;
fn mul(self, other: Relative) -> Relative {
other * self
}
}
impl Div<f64> for Relative {
type Output = Self;
fn div(self, other: f64) -> Self {
Self(self.0 / other)
}
}
assign_impl!(Relative += Relative);
assign_impl!(Relative -= Relative);
assign_impl!(Relative *= f64);
assign_impl!(Relative /= f64);

101
src/geom/sides.rs Normal file
View File

@ -0,0 +1,101 @@
use super::*;
/// A container with left, top, right and bottom components.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct Sides<T> {
/// The value for the left side.
pub left: T,
/// The value for the top side.
pub top: T,
/// The value for the right side.
pub right: T,
/// The value for the bottom side.
pub bottom: T,
}
impl<T> Sides<T> {
/// Create a new box from four sizes.
pub fn new(left: T, top: T, right: T, bottom: T) -> Self {
Self { left, top, right, bottom }
}
/// Create an instance with all four components set to the same `value`.
pub fn uniform(value: T) -> Self
where
T: Clone,
{
Self {
left: value.clone(),
top: value.clone(),
right: value.clone(),
bottom: value,
}
}
}
impl Sides<Linear> {
/// Evaluate the linear values in this container.
pub fn eval(self, size: Size) -> Sides<Length> {
Sides {
left: self.left.eval(size.width),
top: self.top.eval(size.height),
right: self.right.eval(size.width),
bottom: self.bottom.eval(size.height),
}
}
}
impl Sides<Length> {
/// A size with `left` and `right` summed into `width`, and `top` and
/// `bottom` summed into `height`.
pub fn size(self) -> Size {
Size::new(self.left + self.right, self.top + self.bottom)
}
}
impl<T> Get<Side> for Sides<T> {
type Component = T;
fn get(self, side: Side) -> T {
match side {
Side::Left => self.left,
Side::Top => self.top,
Side::Right => self.right,
Side::Bottom => self.bottom,
}
}
fn get_mut(&mut self, side: Side) -> &mut T {
match side {
Side::Left => &mut self.left,
Side::Top => &mut self.top,
Side::Right => &mut self.right,
Side::Bottom => &mut self.bottom,
}
}
}
/// The four sides of objects.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Side {
/// The left side.
Left,
/// The top side.
Top,
/// The right side.
Right,
/// The bottom side.
Bottom,
}
impl Side {
/// The opposite side.
pub fn inv(self) -> Self {
match self {
Self::Left => Self::Right,
Self::Top => Self::Bottom,
Self::Right => Self::Left,
Self::Bottom => Self::Top,
}
}
}

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

@ -0,0 +1,119 @@
use super::*;
/// A size in 2D.
#[derive(Default, Copy, Clone, PartialEq)]
pub struct Size {
/// The width.
pub width: Length,
/// The height.
pub height: Length,
}
impl Size {
/// The zero size.
pub const ZERO: Self = Self {
width: Length::ZERO,
height: Length::ZERO,
};
/// Create a new size from width and height.
pub fn new(width: Length, height: Length) -> Self {
Self { width, height }
}
/// Whether the other size fits into this one (smaller width and height).
pub fn fits(self, other: Self) -> bool {
self.width >= other.width && self.height >= other.height
}
}
impl Get<SpecAxis> for Size {
type Component = Length;
fn get(self, axis: SpecAxis) -> Length {
match axis {
SpecAxis::Horizontal => self.width,
SpecAxis::Vertical => self.height,
}
}
fn get_mut(&mut self, axis: SpecAxis) -> &mut Length {
match axis {
SpecAxis::Horizontal => &mut self.width,
SpecAxis::Vertical => &mut self.height,
}
}
}
impl Switch for Size {
type Other = Gen<Length>;
fn switch(self, dirs: Gen<Dir>) -> Self::Other {
match dirs.main.axis() {
SpecAxis::Horizontal => Gen::new(self.width, self.height),
SpecAxis::Vertical => Gen::new(self.height, self.width),
}
}
}
impl Debug for Size {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "({} x {})", self.width, self.height)
}
}
impl Neg for Size {
type Output = Self;
fn neg(self) -> Self {
Self { width: -self.width, height: -self.height }
}
}
impl Add for Size {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
width: self.width + other.width,
height: self.height + other.height,
}
}
}
sub_impl!(Size - Size -> Size);
impl Mul<f64> for Size {
type Output = Self;
fn mul(self, other: f64) -> Self {
Self {
width: self.width * other,
height: self.height * 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 {
width: self.width / other,
height: self.height / other,
}
}
}
assign_impl!(Size -= Size);
assign_impl!(Size += Size);
assign_impl!(Size *= f64);
assign_impl!(Size /= f64);

105
src/geom/spec.rs Normal file
View File

@ -0,0 +1,105 @@
use super::*;
/// A container with a horizontal and vertical component.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct Spec<T> {
/// The horizontal component.
pub horizontal: T,
/// The vertical component.
pub vertical: T,
}
impl<T> Spec<T> {
/// Create a new instance from the two components.
pub fn new(horizontal: T, vertical: T) -> Self {
Self { horizontal, vertical }
}
}
impl Spec<Length> {
/// The zero value.
pub const ZERO: Self = Self {
horizontal: Length::ZERO,
vertical: Length::ZERO,
};
/// Convert to a point.
pub fn to_point(self) -> Point {
Point::new(self.horizontal, self.vertical)
}
/// Convert to a size.
pub fn to_size(self) -> Size {
Size::new(self.horizontal, self.vertical)
}
}
impl<T> Get<SpecAxis> for Spec<T> {
type Component = T;
fn get(self, axis: SpecAxis) -> T {
match axis {
SpecAxis::Horizontal => self.horizontal,
SpecAxis::Vertical => self.vertical,
}
}
fn get_mut(&mut self, axis: SpecAxis) -> &mut T {
match axis {
SpecAxis::Horizontal => &mut self.horizontal,
SpecAxis::Vertical => &mut self.vertical,
}
}
}
impl<T> Switch for Spec<T> {
type Other = Gen<T>;
fn switch(self, dirs: Gen<Dir>) -> Self::Other {
match dirs.main.axis() {
SpecAxis::Horizontal => Gen::new(self.horizontal, self.vertical),
SpecAxis::Vertical => Gen::new(self.vertical, self.horizontal),
}
}
}
/// The two specific layouting axes.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum SpecAxis {
/// The vertical layouting axis.
Vertical,
/// The horizontal layouting axis.
Horizontal,
}
impl SpecAxis {
/// The other axis.
pub fn other(self) -> Self {
match self {
Self::Horizontal => Self::Vertical,
Self::Vertical => Self::Horizontal,
}
}
}
impl Switch for SpecAxis {
type Other = GenAxis;
fn switch(self, dirs: Gen<Dir>) -> Self::Other {
if self == dirs.main.axis() {
GenAxis::Main
} else {
debug_assert_eq!(self, dirs.cross.axis());
GenAxis::Cross
}
}
}
impl Display for SpecAxis {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::Vertical => "vertical",
Self::Horizontal => "horizontal",
})
}
}

View File

@ -1,6 +1,6 @@
use super::*;
/// The top-level layouting node.
/// The top-level layout node.
#[derive(Debug, Clone, PartialEq)]
pub struct Document {
pub runs: Vec<Pages>,

View File

@ -1,17 +1,28 @@
//! Layouting of documents.
pub mod nodes;
pub mod primitive;
pub use primitive::*;
mod document;
mod fixed;
mod node;
mod pad;
mod par;
mod spacing;
mod stack;
mod text;
use async_trait::async_trait;
use crate::font::SharedFontLoader;
use crate::geom::{Point, Rect, Size, SizeExt};
use crate::geom::*;
use crate::shaping::Shaped;
use nodes::Document;
pub use document::*;
pub use fixed::*;
pub use node::*;
pub use pad::*;
pub use par::*;
pub use spacing::*;
pub use stack::*;
pub use text::*;
/// Layout a document and return the produced layouts.
pub async fn layout(document: &Document, loader: SharedFontLoader) -> Vec<BoxLayout> {
@ -53,9 +64,9 @@ pub trait Layout {
#[derive(Debug, Clone, PartialEq)]
pub enum LayoutItem {
/// Spacing that should be added to the parent.
Spacing(f64),
Spacing(Length),
/// A box that should be aligned in the parent.
Box(BoxLayout, Gen2<GenAlign>),
Box(BoxLayout, Gen<Align>),
}
/// The constraints for layouting a single node.
@ -101,7 +112,7 @@ impl BoxLayout {
/// given position.
pub fn push_layout(&mut self, pos: Point, more: Self) {
for (subpos, element) in more.elements {
self.push(pos + subpos.to_vec2(), element);
self.push(pos + subpos, element);
}
}
}

View File

@ -1,27 +1,9 @@
//! Layout nodes.
mod document;
mod fixed;
mod pad;
mod par;
mod spacing;
mod stack;
mod text;
pub use document::*;
pub use fixed::*;
pub use pad::*;
pub use par::*;
pub use spacing::*;
pub use stack::*;
pub use text::*;
use std::any::Any;
use std::fmt::{self, Debug, Formatter};
use std::ops::Deref;
use async_trait::async_trait;
use super::*;
/// A self-contained, styled layout node.

View File

@ -21,8 +21,8 @@ impl Layout for Pad {
.spaces
.into_iter()
.map(|space| LayoutSpace {
base: space.base + self.padding.insets(space.base).size(),
size: space.size + self.padding.insets(space.size).size(),
base: space.base - self.padding.eval(space.base).size(),
size: space.size - self.padding.eval(space.size).size(),
})
.collect(),
repeat: constraints.repeat,
@ -31,11 +31,11 @@ impl Layout for Pad {
.into_iter()
.map(|item| match item {
LayoutItem::Box(boxed, align) => {
let padding = self.padding.insets(boxed.size);
let padded = boxed.size - padding.size();
let padding = self.padding.eval(boxed.size);
let padded = boxed.size + padding.size();
let mut outer = BoxLayout::new(padded);
let start = Point::new(-padding.x0, -padding.y0);
let start = Point::new(padding.left, padding.top);
outer.push_layout(start, boxed);
LayoutItem::Box(outer, align)

View File

@ -7,11 +7,11 @@ use super::*;
/// the main axis by the height of the previous line plus extra line spacing.
#[derive(Debug, Clone, PartialEq)]
pub struct Par {
pub dirs: Gen2<Dir>,
pub line_spacing: f64,
pub dirs: Gen<Dir>,
pub line_spacing: Length,
pub children: Vec<LayoutNode>,
pub aligns: Gen2<GenAlign>,
pub expand: Spec2<bool>,
pub aligns: Gen<Align>,
pub expand: Spec<bool>,
}
#[async_trait(?Send)]
@ -73,17 +73,17 @@ struct LineLayouter {
#[derive(Debug, Clone)]
struct LineContext {
/// The layout directions.
dirs: Gen2<Dir>,
dirs: Gen<Dir>,
/// The spaces to layout into.
spaces: Vec<LayoutSpace>,
/// Whether to spill over into copies of the last space or finish layouting
/// when the last space is used up.
repeat: bool,
/// The spacing to be inserted between each pair of lines.
line_spacing: f64,
line_spacing: Length,
/// Whether to expand the size of the resulting layout to the full size of
/// this space or to shrink it to fit the content.
expand: Spec2<bool>,
expand: Spec<bool>,
}
impl LineLayouter {
@ -102,7 +102,7 @@ impl LineLayouter {
}
/// Add a layout.
fn push_box(&mut self, layout: BoxLayout, aligns: Gen2<GenAlign>) {
fn push_box(&mut self, layout: BoxLayout, aligns: Gen<Align>) {
let dirs = self.ctx.dirs;
if let Some(prev) = self.run.aligns {
if aligns.main != prev.main {
@ -124,9 +124,9 @@ impl LineLayouter {
// FIXME: Alignment in non-expanding parent.
rest_run.usable = Some(match aligns.cross {
GenAlign::Start => unreachable!("start > x"),
GenAlign::Center => usable - 2.0 * self.run.size.cross,
GenAlign::End => usable - self.run.size.cross,
Align::Start => unreachable!("start > x"),
Align::Center => usable - 2.0 * self.run.size.cross,
Align::End => usable - self.run.size.cross,
});
self.finish_line();
@ -160,7 +160,7 @@ impl LineLayouter {
}
/// Add spacing to the line.
fn push_spacing(&mut self, mut spacing: f64) {
fn push_spacing(&mut self, mut spacing: Length) {
spacing = spacing.min(self.usable().cross);
self.run.size.cross += spacing;
}
@ -169,7 +169,7 @@ impl LineLayouter {
///
/// This specifies how much more would fit before a line break would be
/// needed.
fn usable(&self) -> Gen2<f64> {
fn usable(&self) -> Gen<Length> {
// The base is the usable space of the stack layouter.
let mut usable = self.stack.usable().switch(self.ctx.dirs);
@ -192,7 +192,7 @@ impl LineLayouter {
/// Whether the currently set line is empty.
fn line_is_empty(&self) -> bool {
self.run.size == Gen2::ZERO && self.run.layouts.is_empty()
self.run.size == Gen::ZERO && self.run.layouts.is_empty()
}
/// Finish everything up and return the final collection of boxes.
@ -224,7 +224,7 @@ impl LineLayouter {
self.run.size.cross - offset - child.size.get(dirs.cross.axis())
};
let pos = Gen2::new(0.0, cross).switch(dirs).to_point();
let pos = Gen::new(Length::ZERO, cross).switch(dirs).to_point();
layout.push_layout(pos, child);
}
@ -244,25 +244,25 @@ impl LineLayouter {
/// multiple runs with different alignments.
struct LineRun {
/// The so-far accumulated items of the run.
layouts: Vec<(f64, BoxLayout)>,
layouts: Vec<(Length, BoxLayout)>,
/// The summed width and maximal height of the run.
size: Gen2<f64>,
size: Gen<Length>,
/// The alignment of all layouts in the line.
///
/// When a new run is created the alignment is yet to be determined and
/// `None` as such. Once a layout is added, its alignment decides the
/// alignment for the whole run.
aligns: Option<Gen2<GenAlign>>,
aligns: Option<Gen<Align>>,
/// The amount of cross-space left by another run on the same line or `None`
/// if this is the only run so far.
usable: Option<f64>,
usable: Option<Length>,
}
impl LineRun {
fn new() -> Self {
Self {
layouts: vec![],
size: Gen2::ZERO,
size: Gen::ZERO,
aligns: None,
usable: None,
}
@ -283,7 +283,7 @@ pub(super) struct StackLayouter {
#[derive(Debug, Clone)]
pub(super) struct StackContext {
/// The layouting directions.
pub dirs: Gen2<Dir>,
pub dirs: Gen<Dir>,
/// The spaces to layout into.
pub spaces: Vec<LayoutSpace>,
/// Whether to spill over into copies of the last space or finish layouting
@ -291,7 +291,7 @@ pub(super) struct StackContext {
pub repeat: bool,
/// Whether to expand the size of the resulting layout to the full size of
/// this space or to shrink it to fit the content.
pub expand: Spec2<bool>,
pub expand: Spec<bool>,
}
impl StackLayouter {
@ -306,7 +306,7 @@ impl StackLayouter {
}
/// Add a layout to the stack.
pub fn push_box(&mut self, layout: BoxLayout, aligns: Gen2<GenAlign>) {
pub fn push_box(&mut self, layout: BoxLayout, aligns: Gen<Align>) {
// If the alignment cannot be fitted in this space, finish it.
//
// TODO: Issue warning for non-fitting alignment in non-repeating
@ -331,20 +331,20 @@ impl StackLayouter {
}
/// Add spacing to the stack.
pub fn push_spacing(&mut self, mut spacing: f64) {
pub fn push_spacing(&mut self, mut spacing: Length) {
// Reduce the spacing such that it definitely fits.
let axis = self.ctx.dirs.main.axis();
spacing = spacing.min(self.space.usable.get(axis));
let size = Gen2::new(spacing, 0.0);
let size = Gen::new(spacing, Length::ZERO);
self.update_metrics(size);
self.space.layouts.push((
BoxLayout::new(size.switch(self.ctx.dirs).to_size()),
Gen2::default(),
Gen::default(),
));
}
fn update_metrics(&mut self, added: Gen2<f64>) {
fn update_metrics(&mut self, added: Gen<Length>) {
let mut used = self.space.used.switch(self.ctx.dirs);
used.cross = used.cross.max(added.cross);
used.main += added.main;
@ -398,11 +398,7 @@ impl StackLayouter {
/// Finish active current space and start a new one.
pub fn finish_space(&mut self, hard: bool) {
let dirs = self.ctx.dirs;
// ------------------------------------------------------------------ //
// Step 1: Determine the full size of the space.
// (Mostly done already while collecting the boxes, but here we
// expand if necessary.)
let main = dirs.main.axis();
let space = self.ctx.spaces[self.space.index];
let layout_size = {
@ -416,64 +412,44 @@ impl StackLayouter {
used_size
};
let mut sum = Length::ZERO;
let mut sums = Vec::with_capacity(self.space.layouts.len() + 1);
for (boxed, _) in &self.space.layouts {
sums.push(sum);
sum += boxed.size.get(main);
}
sums.push(sum);
let mut layout = BoxLayout::new(layout_size);
// ------------------------------------------------------------------ //
// Step 2: Forward pass. Create a bounding box for each layout in which
// it will be aligned. Then, go forwards through the boxes and remove
// what is taken by previous layouts from the following layouts.
let mut bounds = vec![];
let mut bound = Rect {
x0: 0.0,
y0: 0.0,
x1: layout_size.width,
y1: layout_size.height,
};
for (layout, _) in &self.space.layouts {
// First, store the bounds calculated so far (which were reduced
// by the predecessors of this layout) as the initial bounding box
// of this layout.
bounds.push(bound);
// Then, reduce the bounding box for the following layouts. This
// layout uses up space from the origin to the end. Thus, it reduces
// the usable space for following layouts at its origin by its
// main-axis extent.
*bound.get_mut(dirs.main.start()) +=
dirs.main.factor() * layout.size.get(dirs.main.axis());
}
// ------------------------------------------------------------------ //
// Step 3: Backward pass. Reduce the bounding boxes from the previous
// layouts by what is taken by the following ones.
let mut main_extent = 0.0;
for (child, bound) in self.space.layouts.iter().zip(&mut bounds).rev() {
let (layout, _) = child;
// Reduce the bounding box of this layout by the following one's
// main-axis extents.
*bound.get_mut(dirs.main.end()) -= dirs.main.factor() * main_extent;
// And then, include this layout's main-axis extent.
main_extent += layout.size.get(dirs.main.axis());
}
// ------------------------------------------------------------------ //
// Step 4: Align each layout in its bounding box and collect everything
// into a single finished layout.
let used = layout_size.switch(dirs);
let children = std::mem::take(&mut self.space.layouts);
for ((child, aligns), bound) in children.into_iter().zip(bounds) {
// Align the child in its own bounds.
let local =
bound.size().anchor(dirs, aligns) - child.size.anchor(dirs, aligns);
for (i, (boxed, aligns)) in children.into_iter().enumerate() {
let size = boxed.size.switch(dirs);
// Make the local position in the bounds global.
let pos = bound.origin() + local;
layout.push_layout(pos, child);
let before = sums[i];
let after = sum - sums[i + 1];
let main_len = used.main - size.main;
let main_range = if dirs.main.is_positive() {
before .. main_len - after
} else {
main_len - before .. after
};
let cross_len = used.cross - size.cross;
let cross_range = if dirs.cross.is_positive() {
Length::ZERO .. cross_len
} else {
cross_len .. Length::ZERO
};
let main = aligns.main.apply(main_range);
let cross = aligns.cross.apply(cross_range);
let pos = Gen::new(main, cross).switch(dirs).to_point();
layout.push_layout(pos, boxed);
}
self.layouts.push(layout);
@ -503,7 +479,7 @@ pub(super) struct Space {
/// Whether to include a layout for this space even if it would be empty.
hard: bool,
/// The so-far accumulated layouts.
layouts: Vec<(BoxLayout, Gen2<GenAlign>)>,
layouts: Vec<(BoxLayout, Gen<Align>)>,
/// The full size of this space.
size: Size,
/// The used size of this space.
@ -511,7 +487,7 @@ pub(super) struct Space {
/// The remaining space.
usable: Size,
/// Which alignments for new boxes are still allowed.
pub(super) allowed_align: GenAlign,
pub(super) allowed_align: Align,
}
impl Space {
@ -523,7 +499,7 @@ impl Space {
size,
used: Size::ZERO,
usable: size,
allowed_align: GenAlign::Start,
allowed_align: Align::Start,
}
}
}

View File

@ -1,510 +0,0 @@
//! Layouting primitives.
use std::fmt::{self, Display, Formatter};
use std::ops::Range;
use crate::geom::{Insets, Linear, Point, Size, Vec2};
/// Generic access to a structure's components.
pub trait Get<Index> {
/// The structure's component type.
type Component;
/// Return the component for the specified index.
fn get(self, index: Index) -> Self::Component;
/// Borrow the component for the specified index mutably.
fn get_mut(&mut self, index: Index) -> &mut Self::Component;
}
/// Switch between the specific and generic representations of a type.
///
/// The generic representation deals with main and cross axes while the specific
/// representation deals with horizontal and vertical axes.
pub trait Switch {
/// The type of the other version.
type Other;
/// The other version of this type based on the current directions.
fn switch(self, dirs: Gen2<Dir>) -> Self::Other;
}
/// The four directions into which content can be laid out.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Dir {
/// Left to right.
LTR,
/// Right to left.
RTL,
/// Top to bottom.
TTB,
/// Bottom to top.
BTT,
}
impl Dir {
/// The specific axis this direction belongs to.
pub fn axis(self) -> SpecAxis {
match self {
Self::LTR | Self::RTL => SpecAxis::Horizontal,
Self::TTB | Self::BTT => SpecAxis::Vertical,
}
}
/// The side this direction starts at.
pub fn start(self) -> Side {
match self {
Self::LTR => Side::Left,
Self::RTL => Side::Right,
Self::TTB => Side::Top,
Self::BTT => Side::Bottom,
}
}
/// The side this direction ends at.
pub fn end(self) -> Side {
match self {
Self::LTR => Side::Right,
Self::RTL => Side::Left,
Self::TTB => Side::Bottom,
Self::BTT => Side::Top,
}
}
/// Whether this direction points into the positive coordinate direction.
///
/// The positive directions are left-to-right and top-to-bottom.
pub fn is_positive(self) -> bool {
match self {
Self::LTR | Self::TTB => true,
Self::RTL | Self::BTT => false,
}
}
/// The factor for this direction.
///
/// - `1.0` if the direction is positive.
/// - `-1.0` if the direction is negative.
pub fn factor(self) -> f64 {
if self.is_positive() { 1.0 } else { -1.0 }
}
/// The inverse direction.
pub fn inv(self) -> Self {
match self {
Self::LTR => Self::RTL,
Self::RTL => Self::LTR,
Self::TTB => Self::BTT,
Self::BTT => Self::TTB,
}
}
}
impl Display for Dir {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::LTR => "ltr",
Self::RTL => "rtl",
Self::TTB => "ttb",
Self::BTT => "btt",
})
}
}
/// A generic container with two components for the two generic axes.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct Gen2<T> {
/// The main component.
pub main: T,
/// The cross component.
pub cross: T,
}
impl<T> Gen2<T> {
/// Create a new instance from the two components.
pub fn new(main: T, cross: T) -> Self {
Self { main, cross }
}
}
impl Gen2<f64> {
/// The instance that has both components set to zero.
pub const ZERO: Self = Self { main: 0.0, cross: 0.0 };
}
impl<T> Get<GenAxis> for Gen2<T> {
type Component = T;
fn get(self, axis: GenAxis) -> T {
match axis {
GenAxis::Main => self.main,
GenAxis::Cross => self.cross,
}
}
fn get_mut(&mut self, axis: GenAxis) -> &mut T {
match axis {
GenAxis::Main => &mut self.main,
GenAxis::Cross => &mut self.cross,
}
}
}
impl<T> Switch for Gen2<T> {
type Other = Spec2<T>;
fn switch(self, dirs: Gen2<Dir>) -> Self::Other {
match dirs.main.axis() {
SpecAxis::Horizontal => Spec2::new(self.main, self.cross),
SpecAxis::Vertical => Spec2::new(self.cross, self.main),
}
}
}
/// A generic container with two components for the two specific axes.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct Spec2<T> {
/// The horizontal component.
pub horizontal: T,
/// The vertical component.
pub vertical: T,
}
impl<T> Spec2<T> {
/// Create a new instance from the two components.
pub fn new(horizontal: T, vertical: T) -> Self {
Self { horizontal, vertical }
}
}
impl Spec2<f64> {
/// The instance that has both components set to zero.
pub const ZERO: Self = Self { horizontal: 0.0, vertical: 0.0 };
/// Convert to a 2D vector.
pub fn to_vec2(self) -> Vec2 {
Vec2::new(self.horizontal, self.vertical)
}
/// Convert to a point.
pub fn to_point(self) -> Point {
Point::new(self.horizontal, self.vertical)
}
/// Convert to a size.
pub fn to_size(self) -> Size {
Size::new(self.horizontal, self.vertical)
}
}
impl<T> Get<SpecAxis> for Spec2<T> {
type Component = T;
fn get(self, axis: SpecAxis) -> T {
match axis {
SpecAxis::Horizontal => self.horizontal,
SpecAxis::Vertical => self.vertical,
}
}
fn get_mut(&mut self, axis: SpecAxis) -> &mut T {
match axis {
SpecAxis::Horizontal => &mut self.horizontal,
SpecAxis::Vertical => &mut self.vertical,
}
}
}
impl<T> Switch for Spec2<T> {
type Other = Gen2<T>;
fn switch(self, dirs: Gen2<Dir>) -> Self::Other {
match dirs.main.axis() {
SpecAxis::Horizontal => Gen2::new(self.horizontal, self.vertical),
SpecAxis::Vertical => Gen2::new(self.vertical, self.horizontal),
}
}
}
/// The two generic layouting axes.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum GenAxis {
/// The axis pages and paragraphs are set along.
Main,
/// The axis words and lines are set along.
Cross,
}
impl GenAxis {
/// The other axis.
pub fn other(self) -> Self {
match self {
Self::Main => Self::Cross,
Self::Cross => Self::Main,
}
}
}
impl Switch for GenAxis {
type Other = SpecAxis;
fn switch(self, dirs: Gen2<Dir>) -> Self::Other {
match self {
Self::Main => dirs.main.axis(),
Self::Cross => dirs.cross.axis(),
}
}
}
impl Display for GenAxis {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::Main => "main",
Self::Cross => "cross",
})
}
}
/// The two specific layouting axes.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum SpecAxis {
/// The vertical layouting axis.
Vertical,
/// The horizontal layouting axis.
Horizontal,
}
impl SpecAxis {
/// The other axis.
pub fn other(self) -> Self {
match self {
Self::Horizontal => Self::Vertical,
Self::Vertical => Self::Horizontal,
}
}
}
impl Switch for SpecAxis {
type Other = GenAxis;
fn switch(self, dirs: Gen2<Dir>) -> Self::Other {
if self == dirs.main.axis() {
GenAxis::Main
} else {
debug_assert_eq!(self, dirs.cross.axis());
GenAxis::Cross
}
}
}
impl Display for SpecAxis {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::Vertical => "vertical",
Self::Horizontal => "horizontal",
})
}
}
/// Where to align content along an axis in a generic context.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum GenAlign {
Start,
Center,
End,
}
impl GenAlign {
/// Returns the position of this alignment in the given length.
pub fn apply(self, range: Range<f64>) -> f64 {
match self {
Self::Start => range.start,
Self::Center => (range.start + range.end) / 2.0,
Self::End => range.end,
}
}
/// The inverse alignment.
pub fn inv(self) -> Self {
match self {
Self::Start => Self::End,
Self::Center => Self::Center,
Self::End => Self::Start,
}
}
}
impl Default for GenAlign {
fn default() -> Self {
Self::Start
}
}
impl Display for GenAlign {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::Start => "start",
Self::Center => "center",
Self::End => "end",
})
}
}
/// Where to align content along an axis in a specific context.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum SpecAlign {
Left,
Right,
Top,
Bottom,
Center,
}
impl SpecAlign {
/// The specific axis this alignment refers to.
///
/// Returns `None` if this is `Center` since the axis is unknown.
pub fn axis(self) -> Option<SpecAxis> {
match self {
Self::Left => Some(SpecAxis::Horizontal),
Self::Right => Some(SpecAxis::Horizontal),
Self::Top => Some(SpecAxis::Vertical),
Self::Bottom => Some(SpecAxis::Vertical),
Self::Center => None,
}
}
/// The inverse alignment.
pub fn inv(self) -> Self {
match self {
Self::Left => Self::Right,
Self::Right => Self::Left,
Self::Top => Self::Bottom,
Self::Bottom => Self::Top,
Self::Center => Self::Center,
}
}
}
impl Switch for SpecAlign {
type Other = GenAlign;
fn switch(self, dirs: Gen2<Dir>) -> Self::Other {
let get = |dir: Dir, at_positive_start| {
if dir.is_positive() == at_positive_start {
GenAlign::Start
} else {
GenAlign::End
}
};
let dirs = dirs.switch(dirs);
match self {
Self::Left => get(dirs.horizontal, true),
Self::Right => get(dirs.horizontal, false),
Self::Top => get(dirs.vertical, true),
Self::Bottom => get(dirs.vertical, false),
Self::Center => GenAlign::Center,
}
}
}
impl Display for SpecAlign {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::Left => "left",
Self::Right => "right",
Self::Top => "top",
Self::Bottom => "bottom",
Self::Center => "center",
})
}
}
/// A generic container with left, top, right and bottom components.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct Sides<T> {
/// The value for the left side.
pub left: T,
/// The value for the top side.
pub top: T,
/// The value for the right side.
pub right: T,
/// The value for the bottom side.
pub bottom: T,
}
impl<T> Sides<T> {
/// Create a new box from four sizes.
pub fn new(left: T, top: T, right: T, bottom: T) -> Self {
Self { left, top, right, bottom }
}
/// Create an instance with all four components set to the same `value`.
pub fn uniform(value: T) -> Self
where
T: Clone,
{
Self {
left: value.clone(),
top: value.clone(),
right: value.clone(),
bottom: value,
}
}
}
impl Sides<Linear> {
/// The absolute insets.
pub fn insets(self, Size { width, height }: Size) -> Insets {
Insets {
x0: -self.left.eval(width),
y0: -self.top.eval(height),
x1: -self.right.eval(width),
y1: -self.bottom.eval(height),
}
}
}
impl<T> Get<Side> for Sides<T> {
type Component = T;
fn get(self, side: Side) -> T {
match side {
Side::Left => self.left,
Side::Top => self.top,
Side::Right => self.right,
Side::Bottom => self.bottom,
}
}
fn get_mut(&mut self, side: Side) -> &mut T {
match side {
Side::Left => &mut self.left,
Side::Top => &mut self.top,
Side::Right => &mut self.right,
Side::Bottom => &mut self.bottom,
}
}
}
/// A side of a container.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Side {
Left,
Top,
Right,
Bottom,
}
impl Side {
/// The opposite side.
pub fn inv(self) -> Self {
match self {
Self::Left => Self::Right,
Self::Top => Self::Bottom,
Self::Right => Self::Left,
Self::Bottom => Self::Top,
}
}
}

View File

@ -5,7 +5,7 @@ use super::*;
/// A node that inserts spacing.
#[derive(Copy, Clone, PartialEq)]
pub struct Spacing {
pub amount: f64,
pub amount: Length,
pub softness: Softness,
}

View File

@ -24,10 +24,10 @@ use super::*;
/// sentence in the second box.
#[derive(Debug, Clone, PartialEq)]
pub struct Stack {
pub dirs: Gen2<Dir>,
pub dirs: Gen<Dir>,
pub children: Vec<LayoutNode>,
pub aligns: Gen2<GenAlign>,
pub expand: Spec2<bool>,
pub aligns: Gen<Align>,
pub expand: Spec<bool>,
}
#[async_trait(?Send)]
@ -90,17 +90,17 @@ impl Layout for Stack {
}
struct StackSpace {
dirs: Gen2<Dir>,
expand: Spec2<bool>,
boxes: Vec<(BoxLayout, Gen2<GenAlign>)>,
dirs: Gen<Dir>,
expand: Spec<bool>,
boxes: Vec<(BoxLayout, Gen<Align>)>,
full_size: Size,
usable: Size,
used: Size,
ruler: GenAlign,
ruler: Align,
}
impl StackSpace {
fn new(dirs: Gen2<Dir>, expand: Spec2<bool>, size: Size) -> Self {
fn new(dirs: Gen<Dir>, expand: Spec<bool>, size: Size) -> Self {
Self {
dirs,
expand,
@ -108,14 +108,14 @@ impl StackSpace {
full_size: size,
usable: size,
used: Size::ZERO,
ruler: GenAlign::Start,
ruler: Align::Start,
}
}
fn push_box(
&mut self,
boxed: BoxLayout,
aligns: Gen2<GenAlign>,
aligns: Gen<Align>,
) -> Result<(), BoxLayout> {
let main = self.dirs.main.axis();
let cross = self.dirs.cross.axis();
@ -133,15 +133,15 @@ impl StackSpace {
Ok(())
}
fn push_spacing(&mut self, spacing: f64) {
fn push_spacing(&mut self, spacing: Length) {
let main = self.dirs.main.axis();
let max = self.usable.get(main);
let trimmed = spacing.min(max);
*self.used.get_mut(main) += trimmed;
*self.usable.get_mut(main) -= trimmed;
let size = Gen2::new(trimmed, 0.0).switch(self.dirs);
self.boxes.push((BoxLayout::new(size.to_size()), Gen2::default()));
let size = Gen::new(trimmed, Length::ZERO).switch(self.dirs);
self.boxes.push((BoxLayout::new(size.to_size()), Gen::default()));
}
fn finish(mut self) -> BoxLayout {
@ -156,7 +156,7 @@ impl StackSpace {
self.used.height = self.full_size.height;
}
let mut sum = 0.0;
let mut sum = Length::ZERO;
let mut sums = Vec::with_capacity(self.boxes.len() + 1);
for (boxed, _) in &self.boxes {
@ -183,14 +183,14 @@ impl StackSpace {
let cross_len = used.cross - size.cross;
let cross_range = if dirs.cross.is_positive() {
0.0 .. cross_len
Length::ZERO .. cross_len
} else {
cross_len .. 0.0
cross_len .. Length::ZERO
};
let main = aligns.main.apply(main_range);
let cross = aligns.cross.apply(cross_range);
let pos = Gen2::new(main, cross).switch(dirs).to_point();
let pos = Gen::new(main, cross).switch(dirs).to_point();
layout.push_layout(pos, boxed);
}

View File

@ -10,11 +10,11 @@ use crate::shaping;
#[derive(Clone, PartialEq)]
pub struct Text {
pub text: String,
pub size: f64,
pub size: Length,
pub dir: Dir,
pub fallback: Rc<FallbackTree>,
pub variant: FontVariant,
pub aligns: Gen2<GenAlign>,
pub aligns: Gen<Align>,
}
#[async_trait(?Send)]

View File

@ -1,203 +0,0 @@
//! A length type with a unit.
use std::fmt::{self, Debug, Display, Formatter};
use std::str::FromStr;
/// A length with a unit.
#[derive(Copy, Clone, PartialEq)]
pub struct Length {
/// The length in the given unit.
pub val: f64,
/// The unit of measurement.
pub unit: Unit,
}
/// Different units of measurement.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum Unit {
/// Points.
Pt,
/// Millimeters.
Mm,
/// Centimeters.
Cm,
/// Inches.
In,
/// Raw units (the implicit unit of all bare `f64` lengths).
Raw,
}
impl Length {
/// Create a length from a value with a unit.
pub const fn new(val: f64, unit: Unit) -> Self {
Self { val, unit }
}
/// Create a length from a number of points.
pub const fn pt(pt: f64) -> Self {
Self::new(pt, Unit::Pt)
}
/// Create a length from a number of millimeters.
pub const fn mm(mm: f64) -> Self {
Self::new(mm, Unit::Mm)
}
/// Create a length from a number of centimeters.
pub const fn cm(cm: f64) -> Self {
Self::new(cm, Unit::Cm)
}
/// Create a length from a number of inches.
pub const fn inches(inches: f64) -> Self {
Self::new(inches, Unit::In)
}
/// Create a length from a number of raw units.
pub const fn raw(raw: f64) -> Self {
Self::new(raw, Unit::Raw)
}
/// Convert this to a number of points.
pub fn as_pt(self) -> f64 {
self.with_unit(Unit::Pt).val
}
/// Convert this to a number of millimeters.
pub fn as_mm(self) -> f64 {
self.with_unit(Unit::Mm).val
}
/// Convert this to a number of centimeters.
pub fn as_cm(self) -> f64 {
self.with_unit(Unit::Cm).val
}
/// Convert this to a number of inches.
pub fn as_inches(self) -> f64 {
self.with_unit(Unit::In).val
}
/// Get the value of this length in raw units.
pub fn as_raw(self) -> f64 {
self.with_unit(Unit::Raw).val
}
/// Convert this to a length with a different unit.
pub fn with_unit(self, unit: Unit) -> Self {
Self {
val: self.val * self.unit.raw_scale() / unit.raw_scale(),
unit,
}
}
}
impl Unit {
/// How many raw units correspond to a value of `1.0` in this unit.
fn raw_scale(self) -> f64 {
match self {
Unit::Pt => 1.0,
Unit::Mm => 2.83465,
Unit::Cm => 28.3465,
Unit::In => 72.0,
Unit::Raw => 1.0,
}
}
}
impl Display for Length {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:.2}{}", self.val, self.unit)
}
}
impl Debug for Length {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
impl Display for Unit {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Unit::Mm => "mm",
Unit::Pt => "pt",
Unit::Cm => "cm",
Unit::In => "in",
Unit::Raw => "rw",
})
}
}
impl Debug for Unit {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
impl FromStr for Length {
type Err = ParseLengthError;
fn from_str(src: &str) -> Result<Self, Self::Err> {
let len = src.len();
// We need at least some number and the unit.
if len <= 2 {
return Err(ParseLengthError);
}
// We can view the string as bytes since a multibyte UTF-8 char cannot
// have valid ASCII chars as subbytes.
let split = len - 2;
let bytes = src.as_bytes();
let unit = match &bytes[split ..] {
b"pt" => Unit::Pt,
b"mm" => Unit::Mm,
b"cm" => Unit::Cm,
b"in" => Unit::In,
_ => return Err(ParseLengthError),
};
src[.. split]
.parse::<f64>()
.map(|val| Self::new(val, unit))
.map_err(|_| ParseLengthError)
}
}
/// The error when parsing a length fails.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct ParseLengthError;
impl std::error::Error for ParseLengthError {}
impl Display for ParseLengthError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad("invalid string for length")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_length_from_str_parses_correct_value_and_unit() {
assert_eq!(Length::from_str("2.5cm"), Ok(Length::cm(2.5)));
}
#[test]
fn test_length_from_str_works_with_non_ascii_chars() {
assert_eq!(Length::from_str("123🚚"), Err(ParseLengthError));
}
#[test]
fn test_length_formats_correctly() {
assert_eq!(Length::cm(12.728).to_string(), "12.73cm".to_string());
}
#[test]
fn test_length_unit_conversion() {
assert!((Length::mm(150.0).as_cm() - 15.0) < 1e-4);
}
}

View File

@ -30,13 +30,13 @@
#[macro_use]
pub mod diag;
pub mod color;
#[macro_use]
pub mod eval;
pub mod color;
pub mod export;
pub mod font;
pub mod geom;
pub mod layout;
pub mod length;
pub mod library;
pub mod paper;
pub mod parse;

View File

@ -1,4 +1,5 @@
use crate::prelude::*;
use std::fmt::{self, Display, Formatter};
/// `align`: Align content along the layouting axes.
///
@ -18,10 +19,10 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value {
let snapshot = ctx.state.clone();
let body = args.find::<SynTree>();
let first = args.get::<_, Spanned<SpecAlign>>(ctx, 0);
let second = args.get::<_, Spanned<SpecAlign>>(ctx, 1);
let hor = args.get::<_, Spanned<SpecAlign>>(ctx, "horizontal");
let ver = args.get::<_, Spanned<SpecAlign>>(ctx, "vertical");
let first = args.get::<_, Spanned<AlignArg>>(ctx, 0);
let second = args.get::<_, Spanned<AlignArg>>(ctx, 1);
let hor = args.get::<_, Spanned<AlignArg>>(ctx, "horizontal");
let ver = args.get::<_, Spanned<AlignArg>>(ctx, "vertical");
args.done(ctx);
let iter = first
@ -50,10 +51,10 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value {
/// Deduplicate alignments and deduce to which axes they apply.
fn dedup_aligns(
ctx: &mut EvalContext,
iter: impl Iterator<Item = (Option<SpecAxis>, Spanned<SpecAlign>)>,
) -> Gen2<GenAlign> {
iter: impl Iterator<Item = (Option<SpecAxis>, Spanned<AlignArg>)>,
) -> Gen<Align> {
let mut aligns = ctx.state.aligns;
let mut had = Gen2::new(false, false);
let mut had = Gen::new(false, false);
let mut had_center = false;
for (axis, Spanned { v: align, span }) in iter {
@ -77,15 +78,15 @@ fn dedup_aligns(
} else {
// We don't know the axis: This has to be a `center` alignment for a
// positional argument.
debug_assert_eq!(align, SpecAlign::Center);
debug_assert_eq!(align, AlignArg::Center);
if had.main && had.cross {
ctx.diag(error!(span, "duplicate alignment"));
} else if had_center {
// Both this and the previous one are unspecified `center`
// alignments. Both axes should be centered.
aligns = Gen2::new(GenAlign::Center, GenAlign::Center);
had = Gen2::new(true, true);
aligns = Gen::new(Align::Center, Align::Center);
had = Gen::new(true, true);
} else {
had_center = true;
}
@ -95,10 +96,10 @@ fn dedup_aligns(
// alignment.
if had_center && (had.main || had.cross) {
if had.main {
aligns.cross = GenAlign::Center;
aligns.cross = Align::Center;
had.cross = true;
} else {
aligns.main = GenAlign::Center;
aligns.main = Align::Center;
had.main = true;
}
had_center = false;
@ -108,8 +109,77 @@ fn dedup_aligns(
// If center has not been flushed by now, it is the only argument and then
// we default to applying it to the cross axis.
if had_center {
aligns.cross = GenAlign::Center;
aligns.cross = Align::Center;
}
aligns
}
/// An alignment argument.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
enum AlignArg {
Left,
Right,
Top,
Bottom,
Center,
}
impl AlignArg {
/// The specific axis this alignment refers to.
///
/// Returns `None` if this is `Center` since the axis is unknown.
pub fn axis(self) -> Option<SpecAxis> {
match self {
Self::Left => Some(SpecAxis::Horizontal),
Self::Right => Some(SpecAxis::Horizontal),
Self::Top => Some(SpecAxis::Vertical),
Self::Bottom => Some(SpecAxis::Vertical),
Self::Center => None,
}
}
}
impl Switch for AlignArg {
type Other = Align;
fn switch(self, dirs: Gen<Dir>) -> Self::Other {
let get = |dir: Dir, at_positive_start| {
if dir.is_positive() == at_positive_start {
Align::Start
} else {
Align::End
}
};
let dirs = dirs.switch(dirs);
match self {
Self::Left => get(dirs.horizontal, true),
Self::Right => get(dirs.horizontal, false),
Self::Top => get(dirs.vertical, true),
Self::Bottom => get(dirs.vertical, false),
Self::Center => Align::Center,
}
}
}
convert_ident!(AlignArg, "alignment", |v| match v {
"left" => Some(Self::Left),
"right" => Some(Self::Right),
"top" => Some(Self::Top),
"bottom" => Some(Self::Bottom),
"center" => Some(Self::Center),
_ => None,
});
impl Display for AlignArg {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Self::Left => "left",
Self::Right => "right",
Self::Top => "top",
Self::Bottom => "bottom",
Self::Center => "center",
})
}
}

View File

@ -1,5 +1,5 @@
use crate::geom::Linear;
use crate::layout::nodes::{Fixed, Stack};
use crate::layout::{Fixed, Stack};
use crate::prelude::*;
/// `box`: Layouts its contents into a box.
@ -33,7 +33,7 @@ pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value {
dirs,
children,
aligns,
expand: Spec2::new(width.is_some(), height.is_some()),
expand: Spec::new(width.is_some(), height.is_some()),
}),
});

View File

@ -57,9 +57,9 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value {
let body = args.find::<SynTree>();
if let Some(linear) = args.find::<Linear>() {
if linear.rel == 0.0 {
if linear.is_absolute() {
ctx.state.text.font_size.base = linear.abs;
ctx.state.text.font_size.scale = Linear::rel(1.0);
ctx.state.text.font_size.scale = Relative::ONE.into();
} else {
ctx.state.text.font_size.scale = linear;
}

View File

@ -1,7 +1,6 @@
use std::mem;
use crate::eval::Absolute;
use crate::geom::Linear;
use crate::geom::{Length, Linear};
use crate::paper::{Paper, PaperClass};
use crate::prelude::*;
@ -25,12 +24,12 @@ pub fn page(mut args: Args, ctx: &mut EvalContext) -> Value {
ctx.state.page.size = paper.size();
}
if let Some(Absolute(width)) = args.get::<_, Absolute>(ctx, "width") {
if let Some(width) = args.get::<_, Length>(ctx, "width") {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.width = width;
}
if let Some(Absolute(height)) = args.get::<_, Absolute>(ctx, "height") {
if let Some(height) = args.get::<_, Length>(ctx, "height") {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.height = height;
}

View File

@ -1,5 +1,5 @@
use crate::geom::Linear;
use crate::layout::nodes::{Softness, Spacing};
use crate::layout::{Softness, Spacing};
use crate::prelude::*;
/// `h`: Add horizontal spacing.

View File

@ -1,18 +1,16 @@
//! Predefined papers.
use crate::geom::{Linear, Size};
use crate::layout::Sides;
use crate::length::Length;
use crate::geom::{Length, Linear, Relative, Sides, Size};
/// Specification of a paper.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Paper {
/// The kind of paper, which defines the default margins.
pub class: PaperClass,
/// The width of the paper.
pub width: Length,
/// The height of the paper.
pub height: Length,
/// The width of the paper in millimeters.
pub width: f64,
/// The height of the paper in millimeters.
pub height: f64,
}
impl Paper {
@ -23,7 +21,7 @@ impl Paper {
/// The size of the paper.
pub fn size(self) -> Size {
Size::new(self.width.as_raw(), self.height.as_raw())
Size::new(Length::mm(self.width), Length::mm(self.height))
}
}
@ -40,7 +38,7 @@ pub enum PaperClass {
impl PaperClass {
/// The default margins for this page class.
pub fn default_margins(self) -> Sides<Linear> {
let f = Linear::rel;
let f = |r| Relative::new(r).into();
let s = |l, r, t, b| Sides::new(f(l), f(r), f(t), f(b));
match self {
Self::Custom => s(0.1190, 0.0842, 0.1190, 0.0842),
@ -69,9 +67,9 @@ macro_rules! papers {
#[doc = $names]
#[doc = "`."]
pub const $var: Paper = Paper {
width: Length::mm($width),
height: Length::mm($height),
class: PaperClass::$class,
width: $width,
height: $height,
};
};
}

View File

@ -432,7 +432,7 @@ fn value(p: &mut Parser) -> Option<Expr> {
Token::Bool(b) => Expr::Lit(Lit::Bool(b)),
Token::Int(i) => Expr::Lit(Lit::Int(i)),
Token::Float(f) => Expr::Lit(Lit::Float(f)),
Token::Length(l) => Expr::Lit(Lit::Length(l)),
Token::Length(val, unit) => Expr::Lit(Lit::Length(val, unit)),
Token::Percent(p) => Expr::Lit(Lit::Percent(p)),
Token::Hex(hex) => Expr::Lit(Lit::Color(color(p, hex, start))),
Token::Str(token) => Expr::Lit(Lit::Str(string(p, token))),

View File

@ -8,7 +8,7 @@ use super::parse;
use crate::color::RgbaColor;
use crate::diag::Deco;
use crate::eval::DictKey;
use crate::length::Length;
use crate::geom::Unit;
use crate::syntax::*;
// ------------------------------ Construct Syntax Nodes ------------------------------ //
@ -51,6 +51,7 @@ macro_rules! F {
use BinOp::*;
use UnOp::*;
use Unit::*;
fn Id(ident: &str) -> Expr {
Expr::Lit(Lit::Ident(Ident(ident.to_string())))
@ -67,8 +68,8 @@ fn Float(float: f64) -> Expr {
fn Percent(percent: f64) -> Expr {
Expr::Lit(Lit::Percent(percent))
}
fn Len(length: Length) -> Expr {
Expr::Lit(Lit::Length(length))
fn Length(val: f64, unit: Unit) -> Expr {
Expr::Lit(Lit::Length(val, unit))
}
fn Color(color: RgbaColor) -> Expr {
Expr::Lit(Lit::Color(color))
@ -347,10 +348,10 @@ fn test_parse_chaining() {
// Things the parser has to make sense of
t!("[hi: (5.0, 2.1 >> you]" => F!("hi"; Dict![Float(5.0), Float(2.1)], Tree![F!("you")]));
t!("[box >> pad: 1pt][Hi]" => F!("box"; Tree![
F!("pad"; Len(Length::pt(1.0)), Tree!(T("Hi")))
F!("pad"; Length(1.0, Pt), Tree!(T("Hi")))
]));
t!("[bold: 400, >> emph >> sub: 1cm]" => F!("bold"; Int(400), Tree![
F!("emph"; Tree!(F!("sub"; Len(Length::cm(1.0)))))
F!("emph"; Tree!(F!("sub"; Length(1.0, Cm))))
]));
// Errors for unclosed / empty predecessor groups
@ -411,8 +412,8 @@ fn test_parse_values() {
v!("1.0e-4" => Float(1e-4));
v!("3.15" => Float(3.15));
v!("50%" => Percent(50.0));
v!("4.5cm" => Len(Length::cm(4.5)));
v!("12e1pt" => Len(Length::pt(12e1)));
v!("4.5cm" => Length(4.5, Cm));
v!("12e1pt" => Length(12e1, Pt));
v!("#f7a20500" => Color(RgbaColor::new(0xf7, 0xa2, 0x05, 0x00)));
v!("\"a\n[]\\\"string\"" => Str("a\n[]\"string"));
@ -446,15 +447,15 @@ fn test_parse_expressions() {
// Operations.
v!("-1" => Unary(Neg, Int(1)));
v!("-- 1" => Unary(Neg, Unary(Neg, Int(1))));
v!("3.2in + 6pt" => Binary(Add, Len(Length::inches(3.2)), Len(Length::pt(6.0))));
v!("3.2in + 6pt" => Binary(Add, Length(3.2, In), Length(6.0, Pt)));
v!("5 - 0.01" => Binary(Sub, Int(5), Float(0.01)));
v!("(3mm * 2)" => Binary(Mul, Len(Length::mm(3.0)), Int(2)));
v!("12e-3cm/1pt" => Binary(Div, Len(Length::cm(12e-3)), Len(Length::pt(1.0))));
v!("(3mm * 2)" => Binary(Mul, Length(3.0, Mm), Int(2)));
v!("12e-3cm/1pt" => Binary(Div, Length(12e-3, Cm), Length(1.0, Pt)));
// More complex.
v!("(3.2in + 6pt)*(5/2-1)" => Binary(
Mul,
Binary(Add, Len(Length::inches(3.2)), Len(Length::pt(6.0))),
Binary(Add, Length(3.2, In), Length(6.0, Pt)),
Binary(Sub, Binary(Div, Int(5), Int(2)), Int(1))
));
v!("(6.3E+2+4* - 3.2pt)/2" => Binary(
@ -462,7 +463,7 @@ fn test_parse_expressions() {
Binary(Add, Float(6.3e2), Binary(
Mul,
Int(4),
Unary(Neg, Len(Length::pt(3.2)))
Unary(Neg, Length(3.2, Pt))
)),
Int(2)
));
@ -483,11 +484,11 @@ fn test_parse_expressions() {
ts!("[val: (1)]" => s(0, 10, F!(s(1, 4, "val"), 5 .. 9; s(6, 9, Int(1)))));
// Invalid expressions.
v!("4pt--" => Len(Length::pt(4.0)));
v!("4pt--" => Length(4.0, Pt));
e!("[val: 4pt--]" => s(10, 11, "missing factor"),
s(6, 10, "missing right summand"));
v!("3mm+4pt*" => Binary(Add, Len(Length::mm(3.0)), Len(Length::pt(4.0))));
v!("3mm+4pt*" => Binary(Add, Length(3.0, Mm), Length(4.0, Pt)));
e!("[val: 3mm+4pt*]" => s(10, 14, "missing right factor"));
}
@ -525,7 +526,7 @@ fn test_parse_dicts_compute_func_calls() {
// More complex.
v!("css(1pt, rgb(90, 102, 254), \"solid\")" => Call!(
"css";
Len(Length::pt(1.0)),
Length(1.0, Pt),
Call!("rgb"; Int(90), Int(102), Int(254)),
Str("solid"),
));
@ -546,7 +547,7 @@ fn test_parse_dicts_nested() {
Int(1),
Dict!(
"ab" => Dict![],
"d" => Dict!(Int(3), Len(Length::pt(14.0))),
"d" => Dict!(Int(3), Length(14.0, Pt)),
),
],
Bool(false),

View File

@ -3,7 +3,7 @@
use std::fmt::{self, Debug, Formatter};
use super::{is_newline, Scanner};
use crate::length::Length;
use crate::geom::Unit;
use crate::syntax::token::*;
use crate::syntax::{is_ident, Pos};
@ -279,8 +279,8 @@ fn parse_expr(text: &str) -> Token<'_> {
Token::Float(num)
} else if let Some(percent) = parse_percent(text) {
Token::Percent(percent)
} else if let Ok(length) = text.parse::<Length>() {
Token::Length(length)
} else if let Some((val, unit)) = parse_length(text) {
Token::Length(val, unit)
} else if is_ident(text) {
Token::Ident(text)
} else {
@ -292,19 +292,41 @@ fn parse_percent(text: &str) -> Option<f64> {
text.strip_suffix('%').and_then(|num| num.parse::<f64>().ok())
}
fn parse_length(text: &str) -> Option<(f64, Unit)> {
let len = text.len();
// We need at least some number and the unit.
if len <= 2 {
return None;
}
// We can view the string as bytes since a multibyte UTF-8 char cannot
// have valid ASCII chars as subbytes.
let split = len - 2;
let bytes = text.as_bytes();
let unit = match &bytes[split ..] {
b"pt" => Unit::Pt,
b"mm" => Unit::Mm,
b"cm" => Unit::Cm,
b"in" => Unit::In,
_ => return None,
};
text[.. split].parse::<f64>().ok().map(|val| (val, unit))
}
#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
use super::*;
use crate::length::Length;
use crate::parse::tests::check;
use Token::{
BlockComment as BC, Bool, Chain, Float, Hex, Hyphen as Min, Ident as Id, Int,
LeftBrace as LB, LeftBracket as L, LeftParen as LP, Length as Len,
LineComment as LC, NonBreakingSpace as Nbsp, Percent, Plus, RightBrace as RB,
RightBracket as R, RightParen as RP, Slash, Space as S, Star, Text as T, *,
BlockComment as BC, Hyphen as Min, Ident as Id, LeftBrace as LB,
LeftBracket as L, LeftParen as LP, LineComment as LC, NonBreakingSpace as Nbsp,
RightBrace as RB, RightBracket as R, RightParen as RP, Space as S, Text as T, *,
};
use Unit::*;
fn Str(string: &str, terminated: bool) -> Token {
Token::Str(TokenStr { string, terminated })
@ -324,6 +346,16 @@ mod tests {
}
}
#[test]
fn test_length_from_str_parses_correct_value_and_unit() {
assert_eq!(parse_length("2.5cm"), Some((2.5, Cm)));
}
#[test]
fn test_length_from_str_works_with_non_ascii_chars() {
assert_eq!(parse_length("123🚚"), None);
}
#[test]
fn tokenize_whitespace() {
t!(Body, "" => );
@ -429,7 +461,7 @@ mod tests {
t!(Header, "🌓, 🌍," => Invalid("🌓"), Comma, S(0), Invalid("🌍"), Comma);
t!(Header, "{abc}" => LB, Id("abc"), RB);
t!(Header, "(1,2)" => LP, Int(1), Comma, Int(2), RP);
t!(Header, "12_pt, 12pt" => Invalid("12_pt"), Comma, S(0), Len(Length::pt(12.0)));
t!(Header, "12_pt, 12pt" => Invalid("12_pt"), Comma, S(0), Length(12.0, Pt));
t!(Header, "f: arg >> g" => Id("f"), Colon, S(0), Id("arg"), S(0), Chain, S(0), Id("g"));
t!(Header, "=3.15" => Equals, Float(3.15));
t!(Header, "arg, _b, _1" => Id("arg"), Comma, S(0), Id("_b"), Comma, S(0), Id("_1"));
@ -446,9 +478,9 @@ mod tests {
t!(Header, "12.3e5" => Float(12.3e5));
t!(Header, "120%" => Percent(120.0));
t!(Header, "12e4%" => Percent(120000.0));
t!(Header, "1e5in" => Len(Length::inches(100000.0)));
t!(Header, "2.3cm" => Len(Length::cm(2.3)));
t!(Header, "02.4mm" => Len(Length::mm(2.4)));
t!(Header, "1e5in" => Length(100000.0, In));
t!(Header, "2.3cm" => Length(2.3, Cm));
t!(Header, "02.4mm" => Length(2.4, Mm));
t!(Header, "2.4.cm" => Invalid("2.4.cm"));
t!(Header, "#6ae6dd" => Hex("6ae6dd"));
t!(Header, "#8A083c" => Hex("8A083c"));
@ -469,11 +501,11 @@ mod tests {
#[test]
fn tokenize_math() {
t!(Header, "12e-3in" => Len(Length::inches(12e-3)));
t!(Header, "12e-3in" => Length(12e-3, In));
t!(Header, "-1" => Min, Int(1));
t!(Header, "--1" => Min, Min, Int(1));
t!(Header, "- 1" => Min, S(0), Int(1));
t!(Header, "6.1cm + 4pt,a=1*2" => Len(Length::cm(6.1)), S(0), Plus, S(0), Len(Length::pt(4.0)),
t!(Header, "6.1cm + 4pt,a=1*2" => Length(6.1, Cm), S(0), Plus, S(0), Length(4.0, Pt),
Comma, Id("a"), Equals, Int(1), Star, Int(2));
t!(Header, "(5 - 1) / 2.1" => LP, Int(5), S(0), Min, S(0), Int(1), RP,
S(0), Slash, S(0), Float(2.1));

View File

@ -3,8 +3,9 @@
pub use crate::diag::{Feedback, Pass};
#[doc(no_inline)]
pub use crate::eval::{Args, Dict, Eval, EvalContext, Value, ValueDict};
pub use crate::layout::nodes::LayoutNode;
pub use crate::layout::primitive::*;
pub use crate::geom::*;
#[doc(no_inline)]
pub use crate::layout::LayoutNode;
#[doc(no_inline)]
pub use crate::syntax::{Span, Spanned, SynTree};
pub use crate::{error, warning};

View File

@ -10,8 +10,8 @@ use fontdock::{FaceId, FaceQuery, FallbackTree, FontVariant};
use ttf_parser::{Face, GlyphId};
use crate::font::FontLoader;
use crate::geom::{Point, Size};
use crate::layout::{BoxLayout, Dir, LayoutElement};
use crate::geom::{Dir, Length, Point, Size};
use crate::layout::{BoxLayout, LayoutElement};
/// A shaped run of text.
#[derive(Clone, PartialEq)]
@ -24,14 +24,14 @@ pub struct Shaped {
pub glyphs: Vec<GlyphId>,
/// The horizontal offsets of the glyphs. This is indexed parallel to `glyphs`.
/// Vertical offets are not yet supported.
pub offsets: Vec<f64>,
pub offsets: Vec<Length>,
/// The font size.
pub size: f64,
pub size: Length,
}
impl Shaped {
/// Create a new shape run with empty `text`, `glyphs` and `offsets`.
pub fn new(face: FaceId, size: f64) -> Self {
pub fn new(face: FaceId, size: Length) -> Self {
Self {
text: String::new(),
face,
@ -63,15 +63,15 @@ impl Debug for Shaped {
/// [`Shaped`]: struct.Shaped.html
pub async fn shape(
text: &str,
size: f64,
size: Length,
dir: Dir,
loader: &mut FontLoader,
fallback: &FallbackTree,
variant: FontVariant,
) -> BoxLayout {
let mut layout = BoxLayout::new(Size::new(0.0, size));
let mut layout = BoxLayout::new(Size::new(Length::ZERO, size));
let mut shaped = Shaped::new(FaceId::MAX, size);
let mut offset = 0.0;
let mut offset = Length::ZERO;
// Create an iterator with conditional direction.
let mut forwards = text.chars();
@ -93,11 +93,11 @@ pub async fn shape(
// Flush the buffer if we change the font face.
if shaped.face != id && !shaped.text.is_empty() {
let pos = Point::new(layout.size.width, 0.0);
let pos = Point::new(layout.size.width, Length::ZERO);
layout.push(pos, LayoutElement::Text(shaped));
layout.size.width += offset;
shaped = Shaped::new(FaceId::MAX, size);
offset = 0.0;
offset = Length::ZERO;
}
shaped.face = id;
@ -110,7 +110,7 @@ pub async fn shape(
// Flush the last buffered parts of the word.
if !shaped.text.is_empty() {
let pos = Point::new(layout.size.width, 0.0);
let pos = Point::new(layout.size.width, Length::ZERO);
layout.push(pos, LayoutElement::Text(shaped));
layout.size.width += offset;
}
@ -120,7 +120,7 @@ pub async fn shape(
/// Looks up the glyph for `c` and returns its index alongside its width at the
/// given `size`.
fn lookup_glyph(face: &Face, c: char, size: f64) -> Option<(GlyphId, f64)> {
fn lookup_glyph(face: &Face, c: char, size: Length) -> Option<(GlyphId, Length)> {
let glyph = face.glyph_index(c)?;
// Determine the width of the char.

View File

@ -3,7 +3,7 @@
use super::*;
use crate::color::RgbaColor;
use crate::eval::DictKey;
use crate::length::Length;
use crate::geom::Unit;
/// A literal.
#[derive(Debug, Clone, PartialEq)]
@ -17,13 +17,13 @@ pub enum Lit {
/// A floating-point literal: `1.2`, `10e-4`.
Float(f64),
/// A length literal: `12pt`, `3cm`.
Length(Length),
Length(f64, Unit),
/// A percent literal: `50%`.
///
/// _Note_: `50%` is represented as `50.0` here, but as `0.5` in the
/// _Note_: `50%` is stored as `50.0` here, but as `0.5` in the
/// corresponding [value].
///
/// [value]: ../../eval/enum.Value.html#variant.Relative
/// [value]: ../../geom/struct.Relative.html
Percent(f64),
/// A color literal: `#ffccee`.
Color(RgbaColor),

View File

@ -1,6 +1,6 @@
//! Token definition.
use crate::length::Length;
use crate::geom::Unit;
/// A minimal semantic entity of source code.
#[derive(Debug, Copy, Clone, PartialEq)]
@ -72,10 +72,10 @@ pub enum Token<'s> {
/// A floating-point number: `1.2`, `10e-4`.
Float(f64),
/// A length: `12pt`, `3cm`.
Length(Length),
Length(f64, Unit),
/// A percentage: `50%`.
///
/// _Note_: `50%` is represented as `50.0` here, as in the corresponding
/// _Note_: `50%` is stored as `50.0` here, as in the corresponding
/// [literal].
///
/// [literal]: ../ast/enum.Lit.html#variant.Percent
@ -159,7 +159,7 @@ impl<'s> Token<'s> {
Self::Bool(_) => "bool",
Self::Int(_) => "integer",
Self::Float(_) => "float",
Self::Length(_) => "length",
Self::Length(..) => "length",
Self::Percent(_) => "percentage",
Self::Hex(_) => "hex value",
Self::Str { .. } => "string",

View File

@ -15,7 +15,7 @@ use typstc::diag::{Feedback, Pass};
use typstc::eval::State;
use typstc::export::pdf;
use typstc::font::{FontLoader, SharedFontLoader};
use typstc::geom::{Point, Vec2};
use typstc::geom::{Length, Point};
use typstc::layout::{BoxLayout, LayoutElement};
use typstc::parse::LineMap;
use typstc::shaping::Shaped;
@ -138,43 +138,40 @@ impl TestFilter {
}
fn render(layouts: &[BoxLayout], loader: &FontLoader, scale: f64) -> DrawTarget {
let pad = scale * 10.0;
let pad = Length::pt(scale * 10.0);
let width = 2.0 * pad
+ layouts
.iter()
.map(|l| scale * l.size.width)
.max_by(|a, b| a.partial_cmp(&b).unwrap())
.unwrap()
.round();
.unwrap();
let height = pad
+ layouts
.iter()
.map(|l| scale * l.size.height + pad)
.sum::<f64>()
.round();
let height =
pad + layouts.iter().map(|l| scale * l.size.height + pad).sum::<Length>();
let mut surface = DrawTarget::new(width as i32, height as i32);
let int_width = width.to_pt().round() as i32;
let int_height = height.to_pt().round() as i32;
let mut surface = DrawTarget::new(int_width, int_height);
surface.clear(BLACK);
let mut offset = Vec2::new(pad, pad);
let mut offset = Point::new(pad, pad);
for layout in layouts {
surface.fill_rect(
offset.x as f32,
offset.y as f32,
(scale * layout.size.width) as f32,
(scale * layout.size.height) as f32,
offset.x.to_pt() as f32,
offset.y.to_pt() as f32,
(scale * layout.size.width).to_pt() as f32,
(scale * layout.size.height).to_pt() as f32,
&Source::Solid(WHITE),
&Default::default(),
);
for (pos, element) in &layout.elements {
for &(pos, ref element) in &layout.elements {
match element {
LayoutElement::Text(shaped) => render_shaped(
&mut surface,
loader,
shaped,
(scale * pos.to_vec2() + offset).to_point(),
scale * pos + offset,
scale,
),
}
@ -205,8 +202,8 @@ fn render_shaped(
let x = pos.x + scale * offset;
let y = pos.y + scale * shaped.size;
let t = Transform::create_scale(s as f32, -s as f32)
.post_translate(Vector::new(x as f32, y as f32));
let t = Transform::create_scale(s.to_pt() as f32, -s.to_pt() as f32)
.post_translate(Vector::new(x.to_pt() as f32, y.to_pt() as f32));
surface.fill(
&path.transform(&t),