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

View File

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

View File

@ -1,7 +1,8 @@
//! Evaluation of syntax trees. //! Evaluation of syntax trees.
mod args; #[macro_use]
mod convert; mod convert;
mod args;
mod dict; mod dict;
mod scope; mod scope;
mod state; mod state;
@ -22,10 +23,10 @@ use fontdock::FontStyle;
use crate::diag::Diag; use crate::diag::Diag;
use crate::diag::{Deco, Feedback, Pass}; 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, Document, LayoutNode, Pad, Pages, Par, Softness, Spacing, Stack, Text,
}; };
use crate::layout::{Gen2, Spec2, Switch};
use crate::syntax::*; use crate::syntax::*;
/// Evaluate a syntax tree into a document. /// Evaluate a syntax tree into a document.
@ -168,7 +169,7 @@ impl EvalContext {
dirs, dirs,
children, children,
aligns, aligns,
expand: Spec2::new(true, true), expand: Spec::new(true, true),
}), }),
}), }),
}) })
@ -195,7 +196,7 @@ impl EvalContext {
line_spacing, line_spacing,
children, children,
aligns, 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, dirs: ctx.state.dirs,
children, children,
aligns: ctx.state.aligns, aligns: ctx.state.aligns,
expand: Spec2::new(false, false), expand: Spec::new(false, false),
}); });
ctx.state.text.fallback = prev; ctx.state.text.fallback = prev;
@ -366,8 +367,8 @@ impl Eval for Lit {
Lit::Bool(v) => Value::Bool(v), Lit::Bool(v) => Value::Bool(v),
Lit::Int(v) => Value::Int(v), Lit::Int(v) => Value::Int(v),
Lit::Float(v) => Value::Float(v), Lit::Float(v) => Value::Float(v),
Lit::Length(v) => Value::Length(v.as_raw()), Lit::Length(v, unit) => Value::Length(Length::with_unit(v, unit)),
Lit::Percent(v) => Value::Relative(v / 100.0), Lit::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
Lit::Color(v) => Value::Color(v), Lit::Color(v) => Value::Color(v),
Lit::Str(ref v) => Value::Str(v.clone()), Lit::Str(ref v) => Value::Str(v.clone()),
Lit::Dict(ref v) => Value::Dict(v.eval(ctx)), 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. /// Compute the sum of two values.
fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value { fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
use crate::geom::Linear as Lin;
use Value::*; use Value::*;
match (lhs, rhs) { match (lhs, rhs) {
// Numbers to themselves. // 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. // Lengths, relatives and linears to themselves.
(Length(a), Length(b)) => Length(a + b), (Length(a), Length(b)) => Length(a + b),
(Length(a), Relative(b)) => Linear(Lin::abs(a) + Lin::rel(b)), (Length(a), Relative(b)) => Linear(a + b),
(Length(a), Linear(b)) => Linear(Lin::abs(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), 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), Length(b)) => Linear(a + b),
(Linear(a), Relative(b)) => Linear(a + Lin::rel(b)), (Linear(a), Relative(b)) => Linear(a + b),
(Linear(a), Linear(b)) => Linear(a + b), (Linear(a), Linear(b)) => Linear(a + b),
// Complex data types to themselves. // 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. /// Compute the difference of two values.
fn sub(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value { fn sub(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
use crate::geom::Linear as Lin;
use Value::*; use Value::*;
match (lhs, rhs) { match (lhs, rhs) {
// Numbers from themselves. // 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. // Lengths, relatives and linears from themselves.
(Length(a), Length(b)) => Length(a - b), (Length(a), Length(b)) => Length(a - b),
(Length(a), Relative(b)) => Linear(Lin::abs(a) - Lin::rel(b)), (Length(a), Relative(b)) => Linear(a - b),
(Length(a), Linear(b)) => Linear(Lin::abs(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), 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), Length(b)) => Linear(a - b),
(Linear(a), Relative(b)) => Linear(a - Lin::rel(b)), (Linear(a), Relative(b)) => Linear(a - b),
(Linear(a), Linear(b)) => Linear(a - b), (Linear(a), Linear(b)) => Linear(a - b),
(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), (Float(a), Linear(b)) => Linear(a * b),
// Integers with strings. // Integers with strings.
(Int(a), Str(b)) => Str(b.repeat(a.max(0) as usize)), (Int(a), Str(b)) => Str(b.repeat(0.max(a) as usize)),
(Str(a), Int(b)) => Str(a.repeat(b.max(0) as usize)), (Str(a), Int(b)) => Str(a.repeat(0.max(b) as usize)),
(a, b) => { (a, b) => {
ctx.diag(error!(span, "cannot multiply {} with {}", a.ty(), b.ty())); 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 fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight};
use super::Scope; use super::Scope;
use crate::geom::{Linear, Size}; use crate::geom::{Align, Dir, Gen, Length, Linear, Relative, Sides, Size};
use crate::layout::{Dir, Gen2, GenAlign, Sides};
use crate::length::Length;
use crate::paper::{Paper, PaperClass, PAPER_A4}; use crate::paper::{Paper, PaperClass, PAPER_A4};
/// The active evaluation state. /// The active evaluation state.
@ -20,9 +18,9 @@ pub struct State {
/// The page state. /// The page state.
pub page: PageState, pub page: PageState,
/// The active layouting directions. /// The active layouting directions.
pub dirs: Gen2<Dir>, pub dirs: Gen<Dir>,
/// The active alignments. /// The active alignments.
pub aligns: Gen2<GenAlign>, pub aligns: Gen<Align>,
} }
impl Default for State { impl Default for State {
@ -31,8 +29,8 @@ impl Default for State {
scope: crate::library::_std(), scope: crate::library::_std(),
text: TextState::default(), text: TextState::default(),
page: PageState::default(), page: PageState::default(),
dirs: Gen2::new(Dir::TTB, Dir::LTR), dirs: Gen::new(Dir::TTB, Dir::LTR),
aligns: Gen2::new(GenAlign::Start, GenAlign::Start), aligns: Gen::new(Align::Start, Align::Start),
} }
} }
} }
@ -62,22 +60,22 @@ pub struct TextState {
impl TextState { impl TextState {
/// The absolute font size. /// The absolute font size.
pub fn font_size(&self) -> f64 { pub fn font_size(&self) -> Length {
self.font_size.eval() self.font_size.eval()
} }
/// The absolute word spacing. /// The absolute word spacing.
pub fn word_spacing(&self) -> f64 { pub fn word_spacing(&self) -> Length {
self.word_spacing.eval(self.font_size()) self.word_spacing.eval(self.font_size())
} }
/// The absolute line spacing. /// The absolute line spacing.
pub fn line_spacing(&self) -> f64 { pub fn line_spacing(&self) -> Length {
self.line_spacing.eval(self.font_size()) self.line_spacing.eval(self.font_size())
} }
/// The absolute paragraph spacing. /// The absolute paragraph spacing.
pub fn par_spacing(&self) -> f64 { pub fn par_spacing(&self) -> Length {
self.par_spacing.eval(self.font_size()) self.par_spacing.eval(self.font_size())
} }
} }
@ -105,10 +103,10 @@ impl Default for TextState {
}, },
strong: false, strong: false,
emph: false, emph: false,
font_size: FontSize::abs(Length::pt(11.0).as_raw()), font_size: FontSize::abs(Length::pt(11.0)),
word_spacing: Linear::rel(0.25), word_spacing: Relative::new(0.25).into(),
line_spacing: Linear::rel(0.2), line_spacing: Relative::new(0.2).into(),
par_spacing: Linear::rel(0.5), par_spacing: Relative::new(0.5).into(),
} }
} }
} }
@ -117,7 +115,7 @@ impl Default for TextState {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct FontSize { pub struct FontSize {
/// The base font size, updated whenever the font size is set absolutely. /// 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 /// The scale to apply on the base font size, updated when the font size
/// is set relatively. /// is set relatively.
pub scale: Linear, pub scale: Linear,
@ -125,17 +123,17 @@ pub struct FontSize {
impl FontSize { impl FontSize {
/// Create a new font size. /// Create a new font size.
pub fn new(base: f64, scale: Linear) -> Self { pub fn new(base: Length, scale: impl Into<Linear>) -> Self {
Self { base, scale } Self { base, scale: scale.into() }
} }
/// Create a new font size with the given `base` and a scale of `1.0`. /// Create a new font size with the given `base` and a scale of `1.0`.
pub fn abs(base: f64) -> Self { pub fn abs(base: Length) -> Self {
Self::new(base, Linear::rel(1.0)) Self::new(base, Relative::ONE)
} }
/// Compute the absolute font size. /// Compute the absolute font size.
pub fn eval(&self) -> f64 { pub fn eval(&self) -> Length {
self.scale.eval(self.base) self.scale.eval(self.base)
} }
} }

View File

@ -6,7 +6,7 @@ use std::rc::Rc;
use super::{Args, Dict, Eval, EvalContext, SpannedEntry}; use super::{Args, Dict, Eval, EvalContext, SpannedEntry};
use crate::color::RgbaColor; use crate::color::RgbaColor;
use crate::geom::Linear; use crate::geom::{Length, Linear, Relative};
use crate::syntax::{Ident, SynTree}; use crate::syntax::{Ident, SynTree};
/// A computational value. /// A computational value.
@ -23,14 +23,9 @@ pub enum Value {
/// A floating-point number: `1.2, 200%`. /// A floating-point number: `1.2, 200%`.
Float(f64), Float(f64),
/// A length: `2cm, 5.2in`. /// A length: `2cm, 5.2in`.
Length(f64), Length(Length),
/// A relative value: `50%`. /// A relative value: `50%`.
/// Relative(Relative),
/// _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),
/// A combination of an absolute length and a relative value: `20% + 5cm`. /// A combination of an absolute length and a relative value: `20% + 5cm`.
Linear(Linear), Linear(Linear),
/// A color value with alpha channel: `#f79143ff`. /// 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 ttf_parser::{name_id, GlyphId};
use crate::font::FontLoader; use crate::font::FontLoader;
use crate::geom::Length;
use crate::layout::{BoxLayout, LayoutElement}; use crate::layout::{BoxLayout, LayoutElement};
use crate::length::Length;
/// Export a list of layouts into a _PDF_ document. /// Export a list of layouts into a _PDF_ document.
/// ///
@ -110,8 +110,8 @@ impl<'a, W: Write> PdfExporter<'a, W> {
let rect = Rect::new( let rect = Rect::new(
0.0, 0.0,
0.0, 0.0,
Length::raw(page.size.width).as_pt() as f32, page.size.width.to_pt() as f32,
Length::raw(page.size.height).as_pt() as f32, page.size.height.to_pt() as f32,
); );
self.writer.write_obj( 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 // Font switching actions are only written when the face used for
// shaped text changes. Hence, we need to remember the active face. // shaped text changes. Hence, we need to remember the active face.
let mut face = FaceId::MAX; let mut face = FaceId::MAX;
let mut size = 0.0; let mut size = Length::ZERO;
for (pos, element) in &page.elements { for (pos, element) in &page.elements {
match element { match element {
@ -147,12 +147,12 @@ impl<'a, W: Write> PdfExporter<'a, W> {
size = shaped.size; size = shaped.size;
text.tf( text.tf(
self.to_pdf[&shaped.face] as u32 + 1, 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 x = pos.x.to_pt();
let y = Length::raw(page.size.height - pos.y - size).as_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.tm(1.0, 0.0, 0.0, 1.0, x as f32, y as f32);
text.tj(shaped.encode_glyphs_be()); 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::*; use super::*;
/// The top-level layouting node. /// The top-level layout node.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Document { pub struct Document {
pub runs: Vec<Pages>, pub runs: Vec<Pages>,

View File

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

View File

@ -21,8 +21,8 @@ impl Layout for Pad {
.spaces .spaces
.into_iter() .into_iter()
.map(|space| LayoutSpace { .map(|space| LayoutSpace {
base: space.base + self.padding.insets(space.base).size(), base: space.base - self.padding.eval(space.base).size(),
size: space.size + self.padding.insets(space.size).size(), size: space.size - self.padding.eval(space.size).size(),
}) })
.collect(), .collect(),
repeat: constraints.repeat, repeat: constraints.repeat,
@ -31,11 +31,11 @@ impl Layout for Pad {
.into_iter() .into_iter()
.map(|item| match item { .map(|item| match item {
LayoutItem::Box(boxed, align) => { LayoutItem::Box(boxed, align) => {
let padding = self.padding.insets(boxed.size); let padding = self.padding.eval(boxed.size);
let padded = boxed.size - padding.size(); let padded = boxed.size + padding.size();
let mut outer = BoxLayout::new(padded); 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); outer.push_layout(start, boxed);
LayoutItem::Box(outer, align) 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. /// the main axis by the height of the previous line plus extra line spacing.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Par { pub struct Par {
pub dirs: Gen2<Dir>, pub dirs: Gen<Dir>,
pub line_spacing: f64, pub line_spacing: Length,
pub children: Vec<LayoutNode>, pub children: Vec<LayoutNode>,
pub aligns: Gen2<GenAlign>, pub aligns: Gen<Align>,
pub expand: Spec2<bool>, pub expand: Spec<bool>,
} }
#[async_trait(?Send)] #[async_trait(?Send)]
@ -73,17 +73,17 @@ struct LineLayouter {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct LineContext { struct LineContext {
/// The layout directions. /// The layout directions.
dirs: Gen2<Dir>, dirs: Gen<Dir>,
/// The spaces to layout into. /// The spaces to layout into.
spaces: Vec<LayoutSpace>, spaces: Vec<LayoutSpace>,
/// Whether to spill over into copies of the last space or finish layouting /// Whether to spill over into copies of the last space or finish layouting
/// when the last space is used up. /// when the last space is used up.
repeat: bool, repeat: bool,
/// The spacing to be inserted between each pair of lines. /// 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 /// Whether to expand the size of the resulting layout to the full size of
/// this space or to shrink it to fit the content. /// this space or to shrink it to fit the content.
expand: Spec2<bool>, expand: Spec<bool>,
} }
impl LineLayouter { impl LineLayouter {
@ -102,7 +102,7 @@ impl LineLayouter {
} }
/// Add a layout. /// 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; let dirs = self.ctx.dirs;
if let Some(prev) = self.run.aligns { if let Some(prev) = self.run.aligns {
if aligns.main != prev.main { if aligns.main != prev.main {
@ -124,9 +124,9 @@ impl LineLayouter {
// FIXME: Alignment in non-expanding parent. // FIXME: Alignment in non-expanding parent.
rest_run.usable = Some(match aligns.cross { rest_run.usable = Some(match aligns.cross {
GenAlign::Start => unreachable!("start > x"), Align::Start => unreachable!("start > x"),
GenAlign::Center => usable - 2.0 * self.run.size.cross, Align::Center => usable - 2.0 * self.run.size.cross,
GenAlign::End => usable - self.run.size.cross, Align::End => usable - self.run.size.cross,
}); });
self.finish_line(); self.finish_line();
@ -160,7 +160,7 @@ impl LineLayouter {
} }
/// Add spacing to the line. /// 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); spacing = spacing.min(self.usable().cross);
self.run.size.cross += spacing; self.run.size.cross += spacing;
} }
@ -169,7 +169,7 @@ impl LineLayouter {
/// ///
/// This specifies how much more would fit before a line break would be /// This specifies how much more would fit before a line break would be
/// needed. /// needed.
fn usable(&self) -> Gen2<f64> { fn usable(&self) -> Gen<Length> {
// The base is the usable space of the stack layouter. // The base is the usable space of the stack layouter.
let mut usable = self.stack.usable().switch(self.ctx.dirs); let mut usable = self.stack.usable().switch(self.ctx.dirs);
@ -192,7 +192,7 @@ impl LineLayouter {
/// Whether the currently set line is empty. /// Whether the currently set line is empty.
fn line_is_empty(&self) -> bool { 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. /// 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()) 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); layout.push_layout(pos, child);
} }
@ -244,25 +244,25 @@ impl LineLayouter {
/// multiple runs with different alignments. /// multiple runs with different alignments.
struct LineRun { struct LineRun {
/// The so-far accumulated items of the run. /// 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. /// The summed width and maximal height of the run.
size: Gen2<f64>, size: Gen<Length>,
/// The alignment of all layouts in the line. /// The alignment of all layouts in the line.
/// ///
/// When a new run is created the alignment is yet to be determined and /// 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 /// `None` as such. Once a layout is added, its alignment decides the
/// alignment for the whole run. /// 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` /// The amount of cross-space left by another run on the same line or `None`
/// if this is the only run so far. /// if this is the only run so far.
usable: Option<f64>, usable: Option<Length>,
} }
impl LineRun { impl LineRun {
fn new() -> Self { fn new() -> Self {
Self { Self {
layouts: vec![], layouts: vec![],
size: Gen2::ZERO, size: Gen::ZERO,
aligns: None, aligns: None,
usable: None, usable: None,
} }
@ -283,7 +283,7 @@ pub(super) struct StackLayouter {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(super) struct StackContext { pub(super) struct StackContext {
/// The layouting directions. /// The layouting directions.
pub dirs: Gen2<Dir>, pub dirs: Gen<Dir>,
/// The spaces to layout into. /// The spaces to layout into.
pub spaces: Vec<LayoutSpace>, pub spaces: Vec<LayoutSpace>,
/// Whether to spill over into copies of the last space or finish layouting /// Whether to spill over into copies of the last space or finish layouting
@ -291,7 +291,7 @@ pub(super) struct StackContext {
pub repeat: bool, pub repeat: bool,
/// Whether to expand the size of the resulting layout to the full size of /// Whether to expand the size of the resulting layout to the full size of
/// this space or to shrink it to fit the content. /// this space or to shrink it to fit the content.
pub expand: Spec2<bool>, pub expand: Spec<bool>,
} }
impl StackLayouter { impl StackLayouter {
@ -306,7 +306,7 @@ impl StackLayouter {
} }
/// Add a layout to the stack. /// 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. // If the alignment cannot be fitted in this space, finish it.
// //
// TODO: Issue warning for non-fitting alignment in non-repeating // TODO: Issue warning for non-fitting alignment in non-repeating
@ -331,20 +331,20 @@ impl StackLayouter {
} }
/// Add spacing to the stack. /// 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. // Reduce the spacing such that it definitely fits.
let axis = self.ctx.dirs.main.axis(); let axis = self.ctx.dirs.main.axis();
spacing = spacing.min(self.space.usable.get(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.update_metrics(size);
self.space.layouts.push(( self.space.layouts.push((
BoxLayout::new(size.switch(self.ctx.dirs).to_size()), 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); let mut used = self.space.used.switch(self.ctx.dirs);
used.cross = used.cross.max(added.cross); used.cross = used.cross.max(added.cross);
used.main += added.main; used.main += added.main;
@ -398,11 +398,7 @@ impl StackLayouter {
/// Finish active current space and start a new one. /// Finish active current space and start a new one.
pub fn finish_space(&mut self, hard: bool) { pub fn finish_space(&mut self, hard: bool) {
let dirs = self.ctx.dirs; let dirs = self.ctx.dirs;
let main = dirs.main.axis();
// ------------------------------------------------------------------ //
// Step 1: Determine the full size of the space.
// (Mostly done already while collecting the boxes, but here we
// expand if necessary.)
let space = self.ctx.spaces[self.space.index]; let space = self.ctx.spaces[self.space.index];
let layout_size = { let layout_size = {
@ -416,64 +412,44 @@ impl StackLayouter {
used_size 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); let mut layout = BoxLayout::new(layout_size);
let used = layout_size.switch(dirs);
// ------------------------------------------------------------------ //
// 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 children = std::mem::take(&mut self.space.layouts); let children = std::mem::take(&mut self.space.layouts);
for ((child, aligns), bound) in children.into_iter().zip(bounds) { for (i, (boxed, aligns)) in children.into_iter().enumerate() {
// Align the child in its own bounds. let size = boxed.size.switch(dirs);
let local =
bound.size().anchor(dirs, aligns) - child.size.anchor(dirs, aligns);
// Make the local position in the bounds global. let before = sums[i];
let pos = bound.origin() + local; let after = sum - sums[i + 1];
layout.push_layout(pos, child); 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); 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. /// Whether to include a layout for this space even if it would be empty.
hard: bool, hard: bool,
/// The so-far accumulated layouts. /// The so-far accumulated layouts.
layouts: Vec<(BoxLayout, Gen2<GenAlign>)>, layouts: Vec<(BoxLayout, Gen<Align>)>,
/// The full size of this space. /// The full size of this space.
size: Size, size: Size,
/// The used size of this space. /// The used size of this space.
@ -511,7 +487,7 @@ pub(super) struct Space {
/// The remaining space. /// The remaining space.
usable: Size, usable: Size,
/// Which alignments for new boxes are still allowed. /// Which alignments for new boxes are still allowed.
pub(super) allowed_align: GenAlign, pub(super) allowed_align: Align,
} }
impl Space { impl Space {
@ -523,7 +499,7 @@ impl Space {
size, size,
used: Size::ZERO, used: Size::ZERO,
usable: size, 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. /// A node that inserts spacing.
#[derive(Copy, Clone, PartialEq)] #[derive(Copy, Clone, PartialEq)]
pub struct Spacing { pub struct Spacing {
pub amount: f64, pub amount: Length,
pub softness: Softness, pub softness: Softness,
} }

View File

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

View File

@ -10,11 +10,11 @@ use crate::shaping;
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct Text { pub struct Text {
pub text: String, pub text: String,
pub size: f64, pub size: Length,
pub dir: Dir, pub dir: Dir,
pub fallback: Rc<FallbackTree>, pub fallback: Rc<FallbackTree>,
pub variant: FontVariant, pub variant: FontVariant,
pub aligns: Gen2<GenAlign>, pub aligns: Gen<Align>,
} }
#[async_trait(?Send)] #[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] #[macro_use]
pub mod diag; pub mod diag;
pub mod color; #[macro_use]
pub mod eval; pub mod eval;
pub mod color;
pub mod export; pub mod export;
pub mod font; pub mod font;
pub mod geom; pub mod geom;
pub mod layout; pub mod layout;
pub mod length;
pub mod library; pub mod library;
pub mod paper; pub mod paper;
pub mod parse; pub mod parse;

View File

@ -1,4 +1,5 @@
use crate::prelude::*; use crate::prelude::*;
use std::fmt::{self, Display, Formatter};
/// `align`: Align content along the layouting axes. /// `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 snapshot = ctx.state.clone();
let body = args.find::<SynTree>(); let body = args.find::<SynTree>();
let first = args.get::<_, Spanned<SpecAlign>>(ctx, 0); let first = args.get::<_, Spanned<AlignArg>>(ctx, 0);
let second = args.get::<_, Spanned<SpecAlign>>(ctx, 1); let second = args.get::<_, Spanned<AlignArg>>(ctx, 1);
let hor = args.get::<_, Spanned<SpecAlign>>(ctx, "horizontal"); let hor = args.get::<_, Spanned<AlignArg>>(ctx, "horizontal");
let ver = args.get::<_, Spanned<SpecAlign>>(ctx, "vertical"); let ver = args.get::<_, Spanned<AlignArg>>(ctx, "vertical");
args.done(ctx); args.done(ctx);
let iter = first 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. /// Deduplicate alignments and deduce to which axes they apply.
fn dedup_aligns( fn dedup_aligns(
ctx: &mut EvalContext, ctx: &mut EvalContext,
iter: impl Iterator<Item = (Option<SpecAxis>, Spanned<SpecAlign>)>, iter: impl Iterator<Item = (Option<SpecAxis>, Spanned<AlignArg>)>,
) -> Gen2<GenAlign> { ) -> Gen<Align> {
let mut aligns = ctx.state.aligns; 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; let mut had_center = false;
for (axis, Spanned { v: align, span }) in iter { for (axis, Spanned { v: align, span }) in iter {
@ -77,15 +78,15 @@ fn dedup_aligns(
} else { } else {
// We don't know the axis: This has to be a `center` alignment for a // We don't know the axis: This has to be a `center` alignment for a
// positional argument. // positional argument.
debug_assert_eq!(align, SpecAlign::Center); debug_assert_eq!(align, AlignArg::Center);
if had.main && had.cross { if had.main && had.cross {
ctx.diag(error!(span, "duplicate alignment")); ctx.diag(error!(span, "duplicate alignment"));
} else if had_center { } else if had_center {
// Both this and the previous one are unspecified `center` // Both this and the previous one are unspecified `center`
// alignments. Both axes should be centered. // alignments. Both axes should be centered.
aligns = Gen2::new(GenAlign::Center, GenAlign::Center); aligns = Gen::new(Align::Center, Align::Center);
had = Gen2::new(true, true); had = Gen::new(true, true);
} else { } else {
had_center = true; had_center = true;
} }
@ -95,10 +96,10 @@ fn dedup_aligns(
// alignment. // alignment.
if had_center && (had.main || had.cross) { if had_center && (had.main || had.cross) {
if had.main { if had.main {
aligns.cross = GenAlign::Center; aligns.cross = Align::Center;
had.cross = true; had.cross = true;
} else { } else {
aligns.main = GenAlign::Center; aligns.main = Align::Center;
had.main = true; had.main = true;
} }
had_center = false; 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 // If center has not been flushed by now, it is the only argument and then
// we default to applying it to the cross axis. // we default to applying it to the cross axis.
if had_center { if had_center {
aligns.cross = GenAlign::Center; aligns.cross = Align::Center;
} }
aligns 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::geom::Linear;
use crate::layout::nodes::{Fixed, Stack}; use crate::layout::{Fixed, Stack};
use crate::prelude::*; use crate::prelude::*;
/// `box`: Layouts its contents into a box. /// `box`: Layouts its contents into a box.
@ -33,7 +33,7 @@ pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value {
dirs, dirs,
children, children,
aligns, 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>(); let body = args.find::<SynTree>();
if let Some(linear) = args.find::<Linear>() { 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.base = linear.abs;
ctx.state.text.font_size.scale = Linear::rel(1.0); ctx.state.text.font_size.scale = Relative::ONE.into();
} else { } else {
ctx.state.text.font_size.scale = linear; ctx.state.text.font_size.scale = linear;
} }

View File

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

View File

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

View File

@ -1,18 +1,16 @@
//! Predefined papers. //! Predefined papers.
use crate::geom::{Linear, Size}; use crate::geom::{Length, Linear, Relative, Sides, Size};
use crate::layout::Sides;
use crate::length::Length;
/// Specification of a paper. /// Specification of a paper.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub struct Paper { pub struct Paper {
/// The kind of paper, which defines the default margins. /// The kind of paper, which defines the default margins.
pub class: PaperClass, pub class: PaperClass,
/// The width of the paper. /// The width of the paper in millimeters.
pub width: Length, pub width: f64,
/// The height of the paper. /// The height of the paper in millimeters.
pub height: Length, pub height: f64,
} }
impl Paper { impl Paper {
@ -23,7 +21,7 @@ impl Paper {
/// The size of the paper. /// The size of the paper.
pub fn size(self) -> Size { 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 { impl PaperClass {
/// The default margins for this page class. /// The default margins for this page class.
pub fn default_margins(self) -> Sides<Linear> { 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)); let s = |l, r, t, b| Sides::new(f(l), f(r), f(t), f(b));
match self { match self {
Self::Custom => s(0.1190, 0.0842, 0.1190, 0.0842), Self::Custom => s(0.1190, 0.0842, 0.1190, 0.0842),
@ -69,9 +67,9 @@ macro_rules! papers {
#[doc = $names] #[doc = $names]
#[doc = "`."] #[doc = "`."]
pub const $var: Paper = Paper { pub const $var: Paper = Paper {
width: Length::mm($width),
height: Length::mm($height),
class: PaperClass::$class, 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::Bool(b) => Expr::Lit(Lit::Bool(b)),
Token::Int(i) => Expr::Lit(Lit::Int(i)), Token::Int(i) => Expr::Lit(Lit::Int(i)),
Token::Float(f) => Expr::Lit(Lit::Float(f)), 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::Percent(p) => Expr::Lit(Lit::Percent(p)),
Token::Hex(hex) => Expr::Lit(Lit::Color(color(p, hex, start))), Token::Hex(hex) => Expr::Lit(Lit::Color(color(p, hex, start))),
Token::Str(token) => Expr::Lit(Lit::Str(string(p, token))), 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::color::RgbaColor;
use crate::diag::Deco; use crate::diag::Deco;
use crate::eval::DictKey; use crate::eval::DictKey;
use crate::length::Length; use crate::geom::Unit;
use crate::syntax::*; use crate::syntax::*;
// ------------------------------ Construct Syntax Nodes ------------------------------ // // ------------------------------ Construct Syntax Nodes ------------------------------ //
@ -51,6 +51,7 @@ macro_rules! F {
use BinOp::*; use BinOp::*;
use UnOp::*; use UnOp::*;
use Unit::*;
fn Id(ident: &str) -> Expr { fn Id(ident: &str) -> Expr {
Expr::Lit(Lit::Ident(Ident(ident.to_string()))) Expr::Lit(Lit::Ident(Ident(ident.to_string())))
@ -67,8 +68,8 @@ fn Float(float: f64) -> Expr {
fn Percent(percent: f64) -> Expr { fn Percent(percent: f64) -> Expr {
Expr::Lit(Lit::Percent(percent)) Expr::Lit(Lit::Percent(percent))
} }
fn Len(length: Length) -> Expr { fn Length(val: f64, unit: Unit) -> Expr {
Expr::Lit(Lit::Length(length)) Expr::Lit(Lit::Length(val, unit))
} }
fn Color(color: RgbaColor) -> Expr { fn Color(color: RgbaColor) -> Expr {
Expr::Lit(Lit::Color(color)) Expr::Lit(Lit::Color(color))
@ -347,10 +348,10 @@ fn test_parse_chaining() {
// Things the parser has to make sense of // 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!("[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![ 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![ 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 // Errors for unclosed / empty predecessor groups
@ -411,8 +412,8 @@ fn test_parse_values() {
v!("1.0e-4" => Float(1e-4)); v!("1.0e-4" => Float(1e-4));
v!("3.15" => Float(3.15)); v!("3.15" => Float(3.15));
v!("50%" => Percent(50.0)); v!("50%" => Percent(50.0));
v!("4.5cm" => Len(Length::cm(4.5))); v!("4.5cm" => Length(4.5, Cm));
v!("12e1pt" => Len(Length::pt(12e1))); v!("12e1pt" => Length(12e1, Pt));
v!("#f7a20500" => Color(RgbaColor::new(0xf7, 0xa2, 0x05, 0x00))); v!("#f7a20500" => Color(RgbaColor::new(0xf7, 0xa2, 0x05, 0x00)));
v!("\"a\n[]\\\"string\"" => Str("a\n[]\"string")); v!("\"a\n[]\\\"string\"" => Str("a\n[]\"string"));
@ -446,15 +447,15 @@ fn test_parse_expressions() {
// Operations. // Operations.
v!("-1" => Unary(Neg, Int(1))); v!("-1" => Unary(Neg, Int(1)));
v!("-- 1" => Unary(Neg, 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!("5 - 0.01" => Binary(Sub, Int(5), Float(0.01)));
v!("(3mm * 2)" => Binary(Mul, Len(Length::mm(3.0)), Int(2))); v!("(3mm * 2)" => Binary(Mul, Length(3.0, Mm), Int(2)));
v!("12e-3cm/1pt" => Binary(Div, Len(Length::cm(12e-3)), Len(Length::pt(1.0)))); v!("12e-3cm/1pt" => Binary(Div, Length(12e-3, Cm), Length(1.0, Pt)));
// More complex. // More complex.
v!("(3.2in + 6pt)*(5/2-1)" => Binary( v!("(3.2in + 6pt)*(5/2-1)" => Binary(
Mul, 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)) Binary(Sub, Binary(Div, Int(5), Int(2)), Int(1))
)); ));
v!("(6.3E+2+4* - 3.2pt)/2" => Binary( v!("(6.3E+2+4* - 3.2pt)/2" => Binary(
@ -462,7 +463,7 @@ fn test_parse_expressions() {
Binary(Add, Float(6.3e2), Binary( Binary(Add, Float(6.3e2), Binary(
Mul, Mul,
Int(4), Int(4),
Unary(Neg, Len(Length::pt(3.2))) Unary(Neg, Length(3.2, Pt))
)), )),
Int(2) 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))))); ts!("[val: (1)]" => s(0, 10, F!(s(1, 4, "val"), 5 .. 9; s(6, 9, Int(1)))));
// Invalid expressions. // Invalid expressions.
v!("4pt--" => Len(Length::pt(4.0))); v!("4pt--" => Length(4.0, Pt));
e!("[val: 4pt--]" => s(10, 11, "missing factor"), e!("[val: 4pt--]" => s(10, 11, "missing factor"),
s(6, 10, "missing right summand")); 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")); e!("[val: 3mm+4pt*]" => s(10, 14, "missing right factor"));
} }
@ -525,7 +526,7 @@ fn test_parse_dicts_compute_func_calls() {
// More complex. // More complex.
v!("css(1pt, rgb(90, 102, 254), \"solid\")" => Call!( v!("css(1pt, rgb(90, 102, 254), \"solid\")" => Call!(
"css"; "css";
Len(Length::pt(1.0)), Length(1.0, Pt),
Call!("rgb"; Int(90), Int(102), Int(254)), Call!("rgb"; Int(90), Int(102), Int(254)),
Str("solid"), Str("solid"),
)); ));
@ -546,7 +547,7 @@ fn test_parse_dicts_nested() {
Int(1), Int(1),
Dict!( Dict!(
"ab" => Dict![], "ab" => Dict![],
"d" => Dict!(Int(3), Len(Length::pt(14.0))), "d" => Dict!(Int(3), Length(14.0, Pt)),
), ),
], ],
Bool(false), Bool(false),

View File

@ -3,7 +3,7 @@
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use super::{is_newline, Scanner}; use super::{is_newline, Scanner};
use crate::length::Length; use crate::geom::Unit;
use crate::syntax::token::*; use crate::syntax::token::*;
use crate::syntax::{is_ident, Pos}; use crate::syntax::{is_ident, Pos};
@ -279,8 +279,8 @@ fn parse_expr(text: &str) -> Token<'_> {
Token::Float(num) Token::Float(num)
} else if let Some(percent) = parse_percent(text) { } else if let Some(percent) = parse_percent(text) {
Token::Percent(percent) Token::Percent(percent)
} else if let Ok(length) = text.parse::<Length>() { } else if let Some((val, unit)) = parse_length(text) {
Token::Length(length) Token::Length(val, unit)
} else if is_ident(text) { } else if is_ident(text) {
Token::Ident(text) Token::Ident(text)
} else { } else {
@ -292,19 +292,41 @@ fn parse_percent(text: &str) -> Option<f64> {
text.strip_suffix('%').and_then(|num| num.parse::<f64>().ok()) 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)] #[cfg(test)]
#[allow(non_snake_case)] #[allow(non_snake_case)]
mod tests { mod tests {
use super::*; use super::*;
use crate::length::Length;
use crate::parse::tests::check; use crate::parse::tests::check;
use Token::{ use Token::{
BlockComment as BC, Bool, Chain, Float, Hex, Hyphen as Min, Ident as Id, Int, BlockComment as BC, Hyphen as Min, Ident as Id, LeftBrace as LB,
LeftBrace as LB, LeftBracket as L, LeftParen as LP, Length as Len, LeftBracket as L, LeftParen as LP, LineComment as LC, NonBreakingSpace as Nbsp,
LineComment as LC, NonBreakingSpace as Nbsp, Percent, Plus, RightBrace as RB, RightBrace as RB, RightBracket as R, RightParen as RP, Space as S, Text as T, *,
RightBracket as R, RightParen as RP, Slash, Space as S, Star, Text as T, *,
}; };
use Unit::*;
fn Str(string: &str, terminated: bool) -> Token { fn Str(string: &str, terminated: bool) -> Token {
Token::Str(TokenStr { string, terminated }) 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] #[test]
fn tokenize_whitespace() { fn tokenize_whitespace() {
t!(Body, "" => ); t!(Body, "" => );
@ -429,7 +461,7 @@ mod tests {
t!(Header, "🌓, 🌍," => Invalid("🌓"), Comma, S(0), Invalid("🌍"), Comma); t!(Header, "🌓, 🌍," => Invalid("🌓"), Comma, S(0), Invalid("🌍"), Comma);
t!(Header, "{abc}" => LB, Id("abc"), RB); t!(Header, "{abc}" => LB, Id("abc"), RB);
t!(Header, "(1,2)" => LP, Int(1), Comma, Int(2), RP); 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, "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, "=3.15" => Equals, Float(3.15));
t!(Header, "arg, _b, _1" => Id("arg"), Comma, S(0), Id("_b"), Comma, S(0), Id("_1")); 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, "12.3e5" => Float(12.3e5));
t!(Header, "120%" => Percent(120.0)); t!(Header, "120%" => Percent(120.0));
t!(Header, "12e4%" => Percent(120000.0)); t!(Header, "12e4%" => Percent(120000.0));
t!(Header, "1e5in" => Len(Length::inches(100000.0))); t!(Header, "1e5in" => Length(100000.0, In));
t!(Header, "2.3cm" => Len(Length::cm(2.3))); t!(Header, "2.3cm" => Length(2.3, Cm));
t!(Header, "02.4mm" => Len(Length::mm(2.4))); t!(Header, "02.4mm" => Length(2.4, Mm));
t!(Header, "2.4.cm" => Invalid("2.4.cm")); t!(Header, "2.4.cm" => Invalid("2.4.cm"));
t!(Header, "#6ae6dd" => Hex("6ae6dd")); t!(Header, "#6ae6dd" => Hex("6ae6dd"));
t!(Header, "#8A083c" => Hex("8A083c")); t!(Header, "#8A083c" => Hex("8A083c"));
@ -469,11 +501,11 @@ mod tests {
#[test] #[test]
fn tokenize_math() { 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, Int(1));
t!(Header, "--1" => Min, Min, Int(1)); t!(Header, "--1" => Min, Min, Int(1));
t!(Header, "- 1" => Min, S(0), 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)); 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, 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)); S(0), Slash, S(0), Float(2.1));

View File

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

View File

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

View File

@ -3,7 +3,7 @@
use super::*; use super::*;
use crate::color::RgbaColor; use crate::color::RgbaColor;
use crate::eval::DictKey; use crate::eval::DictKey;
use crate::length::Length; use crate::geom::Unit;
/// A literal. /// A literal.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -17,13 +17,13 @@ pub enum Lit {
/// A floating-point literal: `1.2`, `10e-4`. /// A floating-point literal: `1.2`, `10e-4`.
Float(f64), Float(f64),
/// A length literal: `12pt`, `3cm`. /// A length literal: `12pt`, `3cm`.
Length(Length), Length(f64, Unit),
/// A percent literal: `50%`. /// 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]. /// corresponding [value].
/// ///
/// [value]: ../../eval/enum.Value.html#variant.Relative /// [value]: ../../geom/struct.Relative.html
Percent(f64), Percent(f64),
/// A color literal: `#ffccee`. /// A color literal: `#ffccee`.
Color(RgbaColor), Color(RgbaColor),

View File

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

View File

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