summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-10-10 22:19:36 +0200
committerLaurenz <laurmaedje@gmail.com>2020-10-10 22:19:36 +0200
commit92c01da36016e94ff20163806ddcbcf7e33d4031 (patch)
tree1a900b3c11edcc93e9153fada3ce92310db5768b
parent42500d5ed85539c5ab04dd3544beaf802da29be9 (diff)
Switch back to custom geometry types, unified with layout primitives 🏞
-rw-r--r--Cargo.toml2
-rw-r--r--src/eval/convert.rs152
-rw-r--r--src/eval/mod.rs47
-rw-r--r--src/eval/state.rs40
-rw-r--r--src/eval/value.rs11
-rw-r--r--src/export/pdf.rs14
-rw-r--r--src/geom.rs235
-rw-r--r--src/geom/align.rs48
-rw-r--r--src/geom/dir.rs83
-rw-r--r--src/geom/gen.rs90
-rw-r--r--src/geom/length.rs213
-rw-r--r--src/geom/linear.rs172
-rw-r--r--src/geom/macros.rs47
-rw-r--r--src/geom/mod.rs53
-rw-r--r--src/geom/point.rs102
-rw-r--r--src/geom/relative.rs92
-rw-r--r--src/geom/sides.rs101
-rw-r--r--src/geom/size.rs119
-rw-r--r--src/geom/spec.rs105
-rw-r--r--src/layout/document.rs (renamed from src/layout/nodes/document.rs)2
-rw-r--r--src/layout/fixed.rs (renamed from src/layout/nodes/fixed.rs)0
-rw-r--r--src/layout/mod.rs29
-rw-r--r--src/layout/node.rs (renamed from src/layout/nodes/mod.rs)18
-rw-r--r--src/layout/pad.rs (renamed from src/layout/nodes/pad.rs)10
-rw-r--r--src/layout/par.rs (renamed from src/layout/nodes/par.rs)146
-rw-r--r--src/layout/primitive.rs510
-rw-r--r--src/layout/spacing.rs (renamed from src/layout/nodes/spacing.rs)2
-rw-r--r--src/layout/stack.rs (renamed from src/layout/nodes/stack.rs)34
-rw-r--r--src/layout/text.rs (renamed from src/layout/nodes/text.rs)4
-rw-r--r--src/length.rs203
-rw-r--r--src/lib.rs4
-rw-r--r--src/library/align.rs96
-rw-r--r--src/library/boxed.rs4
-rw-r--r--src/library/font.rs4
-rw-r--r--src/library/page.rs7
-rw-r--r--src/library/spacing.rs2
-rw-r--r--src/paper.rs20
-rw-r--r--src/parse/mod.rs2
-rw-r--r--src/parse/tests.rs33
-rw-r--r--src/parse/tokens.rs60
-rw-r--r--src/prelude.rs5
-rw-r--r--src/shaping.rs24
-rw-r--r--src/syntax/ast/lit.rs8
-rw-r--r--src/syntax/token.rs8
-rw-r--r--tests/test_typeset.rs37
45 files changed, 1656 insertions, 1342 deletions
diff --git a/Cargo.toml b/Cargo.toml
index a9d8011d..96385771 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,7 +24,6 @@ lto = true
[dependencies]
async-trait = "0.1"
fontdock = { path = "../fontdock", default-features = false }
-kurbo = "0.6.3"
tide = { path = "../tide" }
ttf-parser = "0.8.2"
unicode-xid = "0.2"
@@ -33,6 +32,7 @@ serde = { version = "1", features = ["derive"], optional = true }
[dev-dependencies]
criterion = "0.3"
futures-executor = "0.3"
+kurbo = "0.6.3"
serde_json = "1"
raqote = { version = "0.8", default-features = false }
diff --git a/src/eval/convert.rs b/src/eval/convert.rs
index 4c177c5b..69ef2506 100644
--- a/src/eval/convert.rs
+++ b/src/eval/convert.rs
@@ -6,8 +6,7 @@ use fontdock::{FontStretch, FontStyle, FontWeight};
use super::{Value, ValueDict, ValueFunc};
use crate::diag::Diag;
-use crate::geom::Linear;
-use crate::layout::{Dir, SpecAlign};
+use crate::geom::{Dir, Length, Linear, Relative};
use crate::paper::Paper;
use crate::syntax::{Ident, SpanWith, Spanned, SynTree};
@@ -37,26 +36,53 @@ impl<T: Convert> Convert for Spanned<T> {
}
}
-/// A value type that matches [length] values.
-///
-/// [length]: enum.Value.html#variant.Length
-pub struct Absolute(pub f64);
-
-impl From<Absolute> for f64 {
- fn from(abs: Absolute) -> f64 {
- abs.0
- }
+macro_rules! convert_match {
+ ($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
+ impl $crate::eval::Convert for $type {
+ fn convert(
+ value: $crate::syntax::Spanned<$crate::eval::Value>
+ ) -> (Result<Self, $crate::eval::Value>, Option<$crate::diag::Diag>) {
+ #[allow(unreachable_patterns)]
+ match value.v {
+ $($p => (Ok($r), None)),*,
+ v => {
+ let err = $crate::error!("expected {}, found {}", $name, v.ty());
+ (Err(v), Some(err))
+ },
+ }
+ }
+ }
+ };
}
-/// A value type that matches [relative] values.
-///
-/// [relative]: enum.Value.html#variant.Relative
-pub struct Relative(pub f64);
-
-impl From<Relative> for f64 {
- fn from(rel: Relative) -> f64 {
- rel.0
- }
+macro_rules! convert_ident {
+ ($type:ty, $name:expr, $parse:expr) => {
+ impl $crate::eval::Convert for $type {
+ fn convert(
+ value: $crate::syntax::Spanned<$crate::eval::Value>,
+ ) -> (
+ Result<Self, $crate::eval::Value>,
+ Option<$crate::diag::Diag>,
+ ) {
+ match value.v {
+ Value::Ident(id) => {
+ if let Some(thing) = $parse(&id) {
+ (Ok(thing), None)
+ } else {
+ (
+ Err($crate::eval::Value::Ident(id)),
+ Some($crate::error!("invalid {}", $name)),
+ )
+ }
+ }
+ v => {
+ let err = $crate::error!("expected {}, found {}", $name, v.ty());
+ (Err(v), Some(err))
+ }
+ }
+ }
+ }
+ };
}
/// A value type that matches [identifier] and [string] values.
@@ -79,70 +105,31 @@ impl Deref for StringLike {
}
}
-macro_rules! impl_match {
- ($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
- impl Convert for $type {
- fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>) {
- #[allow(unreachable_patterns)]
- match value.v {
- $($p => (Ok($r), None)),*,
- v => {
- let err = error!("expected {}, found {}", $name, v.ty());
- (Err(v), Some(err))
- },
- }
- }
- }
- };
-}
-
-impl_match!(Value, "value", v => v);
-impl_match!(Ident, "identifier", Value::Ident(v) => v);
-impl_match!(bool, "bool", Value::Bool(v) => v);
-impl_match!(i64, "integer", Value::Int(v) => v);
-impl_match!(f64, "float",
+convert_match!(Value, "value", v => v);
+convert_match!(Ident, "identifier", Value::Ident(v) => v);
+convert_match!(bool, "bool", Value::Bool(v) => v);
+convert_match!(i64, "integer", Value::Int(v) => v);
+convert_match!(f64, "float",
Value::Int(v) => v as f64,
Value::Float(v) => v,
);
-impl_match!(Absolute, "length", Value::Length(v) => Absolute(v));
-impl_match!(Relative, "relative", Value::Relative(v) => Relative(v));
-impl_match!(Linear, "linear",
+convert_match!(Length, "length", Value::Length(v) => v);
+convert_match!(Relative, "relative", Value::Relative(v) => v);
+convert_match!(Linear, "linear",
Value::Linear(v) => v,
- Value::Length(v) => Linear::abs(v),
- Value::Relative(v) => Linear::rel(v),
+ Value::Length(v) => v.into(),
+ Value::Relative(v) => v.into(),
);
-impl_match!(String, "string", Value::Str(v) => v);
-impl_match!(SynTree, "tree", Value::Content(v) => v);
-impl_match!(ValueDict, "dictionary", Value::Dict(v) => v);
-impl_match!(ValueFunc, "function", Value::Func(v) => v);
-impl_match!(StringLike, "identifier or string",
+convert_match!(String, "string", Value::Str(v) => v);
+convert_match!(SynTree, "tree", Value::Content(v) => v);
+convert_match!(ValueDict, "dictionary", Value::Dict(v) => v);
+convert_match!(ValueFunc, "function", Value::Func(v) => v);
+convert_match!(StringLike, "identifier or string",
Value::Ident(Ident(v)) => StringLike(v),
Value::Str(v) => StringLike(v),
);
-macro_rules! impl_ident {
- ($type:ty, $name:expr, $parse:expr) => {
- impl Convert for $type {
- fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>) {
- match value.v {
- Value::Ident(id) => {
- if let Some(thing) = $parse(&id) {
- (Ok(thing), None)
- } else {
- (Err(Value::Ident(id)), Some(error!("invalid {}", $name)))
- }
- }
- v => {
- let err = error!("expected {}, found {}", $name, v.ty());
- (Err(v), Some(err))
- }
- }
- }
- }
- };
-}
-
-impl_ident!(Dir, "direction", |v| match v {
+convert_ident!(Dir, "direction", |v| match v {
"ltr" => Some(Self::LTR),
"rtl" => Some(Self::RTL),
"ttb" => Some(Self::TTB),
@@ -150,18 +137,9 @@ impl_ident!(Dir, "direction", |v| match v {
_ => None,
});
-impl_ident!(SpecAlign, "alignment", |v| match v {
- "left" => Some(Self::Left),
- "right" => Some(Self::Right),
- "top" => Some(Self::Top),
- "bottom" => Some(Self::Bottom),
- "center" => Some(Self::Center),
- _ => None,
-});
-
-impl_ident!(FontStyle, "font style", Self::from_str);
-impl_ident!(FontStretch, "font stretch", Self::from_str);
-impl_ident!(Paper, "paper", Self::from_name);
+convert_ident!(FontStyle, "font style", Self::from_str);
+convert_ident!(FontStretch, "font stretch", Self::from_str);
+convert_ident!(Paper, "paper", Self::from_name);
impl Convert for FontWeight {
fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>) {
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 2c6f4d7c..fc8bbbd6 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -1,7 +1,8 @@
//! Evaluation of syntax trees.
-mod args;
+#[macro_use]
mod convert;
+mod args;
mod dict;
mod scope;
mod state;
@@ -22,10 +23,10 @@ use fontdock::FontStyle;
use crate::diag::Diag;
use crate::diag::{Deco, Feedback, Pass};
-use crate::layout::nodes::{
+use crate::geom::{Gen, Length, Relative, Spec, Switch};
+use crate::layout::{
Document, LayoutNode, Pad, Pages, Par, Softness, Spacing, Stack, Text,
};
-use crate::layout::{Gen2, Spec2, Switch};
use crate::syntax::*;
/// Evaluate a syntax tree into a document.
@@ -168,7 +169,7 @@ impl EvalContext {
dirs,
children,
aligns,
- expand: Spec2::new(true, true),
+ expand: Spec::new(true, true),
}),
}),
})
@@ -195,7 +196,7 @@ impl EvalContext {
line_spacing,
children,
aligns,
- expand: Gen2::new(false, expand_cross).switch(dirs),
+ expand: Gen::new(false, expand_cross).switch(dirs),
});
}
}
@@ -337,7 +338,7 @@ impl Eval for NodeRaw {
dirs: ctx.state.dirs,
children,
aligns: ctx.state.aligns,
- expand: Spec2::new(false, false),
+ expand: Spec::new(false, false),
});
ctx.state.text.fallback = prev;
@@ -366,8 +367,8 @@ impl Eval for Lit {
Lit::Bool(v) => Value::Bool(v),
Lit::Int(v) => Value::Int(v),
Lit::Float(v) => Value::Float(v),
- Lit::Length(v) => Value::Length(v.as_raw()),
- Lit::Percent(v) => Value::Relative(v / 100.0),
+ Lit::Length(v, unit) => Value::Length(Length::with_unit(v, unit)),
+ Lit::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
Lit::Color(v) => Value::Color(v),
Lit::Str(ref v) => Value::Str(v.clone()),
Lit::Dict(ref v) => Value::Dict(v.eval(ctx)),
@@ -473,7 +474,6 @@ fn neg(ctx: &mut EvalContext, span: Span, value: Value) -> Value {
/// Compute the sum of two values.
fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
- use crate::geom::Linear as Lin;
use Value::*;
match (lhs, rhs) {
// Numbers to themselves.
@@ -484,15 +484,15 @@ fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
// Lengths, relatives and linears to themselves.
(Length(a), Length(b)) => Length(a + b),
- (Length(a), Relative(b)) => Linear(Lin::abs(a) + Lin::rel(b)),
- (Length(a), Linear(b)) => Linear(Lin::abs(a) + b),
+ (Length(a), Relative(b)) => Linear(a + b),
+ (Length(a), Linear(b)) => Linear(a + b),
- (Relative(a), Length(b)) => Linear(Lin::rel(a) + Lin::abs(b)),
+ (Relative(a), Length(b)) => Linear(a + b),
(Relative(a), Relative(b)) => Relative(a + b),
- (Relative(a), Linear(b)) => Linear(Lin::rel(a) + b),
+ (Relative(a), Linear(b)) => Linear(a + b),
- (Linear(a), Length(b)) => Linear(a + Lin::abs(b)),
- (Linear(a), Relative(b)) => Linear(a + Lin::rel(b)),
+ (Linear(a), Length(b)) => Linear(a + b),
+ (Linear(a), Relative(b)) => Linear(a + b),
(Linear(a), Linear(b)) => Linear(a + b),
// Complex data types to themselves.
@@ -509,7 +509,6 @@ fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
/// Compute the difference of two values.
fn sub(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
- use crate::geom::Linear as Lin;
use Value::*;
match (lhs, rhs) {
// Numbers from themselves.
@@ -520,13 +519,13 @@ fn sub(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
// Lengths, relatives and linears from themselves.
(Length(a), Length(b)) => Length(a - b),
- (Length(a), Relative(b)) => Linear(Lin::abs(a) - Lin::rel(b)),
- (Length(a), Linear(b)) => Linear(Lin::abs(a) - b),
- (Relative(a), Length(b)) => Linear(Lin::rel(a) - Lin::abs(b)),
+ (Length(a), Relative(b)) => Linear(a - b),
+ (Length(a), Linear(b)) => Linear(a - b),
+ (Relative(a), Length(b)) => Linear(a - b),
(Relative(a), Relative(b)) => Relative(a - b),
- (Relative(a), Linear(b)) => Linear(Lin::rel(a) - b),
- (Linear(a), Length(b)) => Linear(a - Lin::abs(b)),
- (Linear(a), Relative(b)) => Linear(a - Lin::rel(b)),
+ (Relative(a), Linear(b)) => Linear(a - b),
+ (Linear(a), Length(b)) => Linear(a - b),
+ (Linear(a), Relative(b)) => Linear(a - b),
(Linear(a), Linear(b)) => Linear(a - b),
(a, b) => {
@@ -561,8 +560,8 @@ fn mul(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
(Float(a), Linear(b)) => Linear(a * b),
// Integers with strings.
- (Int(a), Str(b)) => Str(b.repeat(a.max(0) as usize)),
- (Str(a), Int(b)) => Str(a.repeat(b.max(0) as usize)),
+ (Int(a), Str(b)) => Str(b.repeat(0.max(a) as usize)),
+ (Str(a), Int(b)) => Str(a.repeat(0.max(b) as usize)),
(a, b) => {
ctx.diag(error!(span, "cannot multiply {} with {}", a.ty(), b.ty()));
diff --git a/src/eval/state.rs b/src/eval/state.rs
index 7372b851..3ae6b414 100644
--- a/src/eval/state.rs
+++ b/src/eval/state.rs
@@ -5,9 +5,7 @@ use std::rc::Rc;
use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight};
use super::Scope;
-use crate::geom::{Linear, Size};
-use crate::layout::{Dir, Gen2, GenAlign, Sides};
-use crate::length::Length;
+use crate::geom::{Align, Dir, Gen, Length, Linear, Relative, Sides, Size};
use crate::paper::{Paper, PaperClass, PAPER_A4};
/// The active evaluation state.
@@ -20,9 +18,9 @@ pub struct State {
/// The page state.
pub page: PageState,
/// The active layouting directions.
- pub dirs: Gen2<Dir>,
+ pub dirs: Gen<Dir>,
/// The active alignments.
- pub aligns: Gen2<GenAlign>,
+ pub aligns: Gen<Align>,
}
impl Default for State {
@@ -31,8 +29,8 @@ impl Default for State {
scope: crate::library::_std(),
text: TextState::default(),
page: PageState::default(),
- dirs: Gen2::new(Dir::TTB, Dir::LTR),
- aligns: Gen2::new(GenAlign::Start, GenAlign::Start),
+ dirs: Gen::new(Dir::TTB, Dir::LTR),
+ aligns: Gen::new(Align::Start, Align::Start),
}
}
}
@@ -62,22 +60,22 @@ pub struct TextState {
impl TextState {
/// The absolute font size.
- pub fn font_size(&self) -> f64 {
+ pub fn font_size(&self) -> Length {
self.font_size.eval()
}
/// The absolute word spacing.
- pub fn word_spacing(&self) -> f64 {
+ pub fn word_spacing(&self) -> Length {
self.word_spacing.eval(self.font_size())
}
/// The absolute line spacing.
- pub fn line_spacing(&self) -> f64 {
+ pub fn line_spacing(&self) -> Length {
self.line_spacing.eval(self.font_size())
}
/// The absolute paragraph spacing.
- pub fn par_spacing(&self) -> f64 {
+ pub fn par_spacing(&self) -> Length {
self.par_spacing.eval(self.font_size())
}
}
@@ -105,10 +103,10 @@ impl Default for TextState {
},
strong: false,
emph: false,
- font_size: FontSize::abs(Length::pt(11.0).as_raw()),
- word_spacing: Linear::rel(0.25),
- line_spacing: Linear::rel(0.2),
- par_spacing: Linear::rel(0.5),
+ font_size: FontSize::abs(Length::pt(11.0)),
+ word_spacing: Relative::new(0.25).into(),
+ line_spacing: Relative::new(0.2).into(),
+ par_spacing: Relative::new(0.5).into(),
}
}
}
@@ -117,7 +115,7 @@ impl Default for TextState {
#[derive(Debug, Clone, PartialEq)]
pub struct FontSize {
/// The base font size, updated whenever the font size is set absolutely.
- pub base: f64,
+ pub base: Length,
/// The scale to apply on the base font size, updated when the font size
/// is set relatively.
pub scale: Linear,
@@ -125,17 +123,17 @@ pub struct FontSize {
impl FontSize {
/// Create a new font size.
- pub fn new(base: f64, scale: Linear) -> Self {
- Self { base, scale }
+ pub fn new(base: Length, scale: impl Into<Linear>) -> Self {
+ Self { base, scale: scale.into() }
}
/// Create a new font size with the given `base` and a scale of `1.0`.
- pub fn abs(base: f64) -> Self {
- Self::new(base, Linear::rel(1.0))
+ pub fn abs(base: Length) -> Self {
+ Self::new(base, Relative::ONE)
}
/// Compute the absolute font size.
- pub fn eval(&self) -> f64 {
+ pub fn eval(&self) -> Length {
self.scale.eval(self.base)
}
}
diff --git a/src/eval/value.rs b/src/eval/value.rs
index c4b11ebe..56dadfc3 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -6,7 +6,7 @@ use std::rc::Rc;
use super::{Args, Dict, Eval, EvalContext, SpannedEntry};
use crate::color::RgbaColor;
-use crate::geom::Linear;
+use crate::geom::{Length, Linear, Relative};
use crate::syntax::{Ident, SynTree};
/// A computational value.
@@ -23,14 +23,9 @@ pub enum Value {
/// A floating-point number: `1.2, 200%`.
Float(f64),
/// A length: `2cm, 5.2in`.
- Length(f64),
+ Length(Length),
/// A relative value: `50%`.
- ///
- /// _Note_: `50%` is represented as `0.5` here, but as `50.0` in the
- /// corresponding [literal].
- ///
- /// [literal]: ../syntax/ast/enum.Lit.html#variant.Percent
- Relative(f64),
+ Relative(Relative),
/// A combination of an absolute length and a relative value: `20% + 5cm`.
Linear(Linear),
/// A color value with alpha channel: `#f79143ff`.
diff --git a/src/export/pdf.rs b/src/export/pdf.rs
index 02b54471..722728ff 100644
--- a/src/export/pdf.rs
+++ b/src/export/pdf.rs
@@ -14,8 +14,8 @@ use tide::{PdfWriter, Rect, Ref, Trailer, Version};
use ttf_parser::{name_id, GlyphId};
use crate::font::FontLoader;
+use crate::geom::Length;
use crate::layout::{BoxLayout, LayoutElement};
-use crate::length::Length;
/// Export a list of layouts into a _PDF_ document.
///
@@ -110,8 +110,8 @@ impl<'a, W: Write> PdfExporter<'a, W> {
let rect = Rect::new(
0.0,
0.0,
- Length::raw(page.size.width).as_pt() as f32,
- Length::raw(page.size.height).as_pt() as f32,
+ page.size.width.to_pt() as f32,
+ page.size.height.to_pt() as f32,
);
self.writer.write_obj(
@@ -136,7 +136,7 @@ impl<'a, W: Write> PdfExporter<'a, W> {
// Font switching actions are only written when the face used for
// shaped text changes. Hence, we need to remember the active face.
let mut face = FaceId::MAX;
- let mut size = 0.0;
+ let mut size = Length::ZERO;
for (pos, element) in &page.elements {
match element {
@@ -147,12 +147,12 @@ impl<'a, W: Write> PdfExporter<'a, W> {
size = shaped.size;
text.tf(
self.to_pdf[&shaped.face] as u32 + 1,
- Length::raw(size).as_pt() as f32,
+ size.to_pt() as f32,
);
}
- let x = Length::raw(pos.x).as_pt();
- let y = Length::raw(page.size.height - pos.y - size).as_pt();
+ let x = pos.x.to_pt();
+ let y = (page.size.height - pos.y - size).to_pt();
text.tm(1.0, 0.0, 0.0, 1.0, x as f32, y as f32);
text.tj(shaped.encode_glyphs_be());
}
diff --git a/src/geom.rs b/src/geom.rs
deleted file mode 100644
index 90c7ac62..00000000
--- a/src/geom.rs
+++ /dev/null
@@ -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)
- }
-}
diff --git a/src/geom/align.rs b/src/geom/align.rs
new file mode 100644
index 00000000..1030a133
--- /dev/null
+++ b/src/geom/align.rs
@@ -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",
+ })
+ }
+}
diff --git a/src/geom/dir.rs b/src/geom/dir.rs
new file mode 100644
index 00000000..cfcb4c09
--- /dev/null
+++ b/src/geom/dir.rs
@@ -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",
+ })
+ }
+}
diff --git a/src/geom/gen.rs b/src/geom/gen.rs
new file mode 100644
index 00000000..d877713b
--- /dev/null
+++ b/src/geom/gen.rs
@@ -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",
+ })
+ }
+}
diff --git a/src/geom/length.rs b/src/geom/length.rs
new file mode 100644
index 00000000..60ccce2b
--- /dev/null
+++ b/src/geom/length.rs
@@ -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);
+ }
+}
diff --git a/src/geom/linear.rs b/src/geom/linear.rs
new file mode 100644
index 00000000..2567d264
--- /dev/null
+++ b/src/geom/linear.rs
@@ -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);
diff --git a/src/geom/macros.rs b/src/geom/macros.rs
new file mode 100644
index 00000000..615eb31c
--- /dev/null
+++ b/src/geom/macros.rs
@@ -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;
+ }
+ }
+ };
+}
diff --git a/src/geom/mod.rs b/src/geom/mod.rs
new file mode 100644
index 00000000..c9c3040c
--- /dev/null
+++ b/src/geom/mod.rs
@@ -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;
+}
diff --git a/src/geom/point.rs b/src/geom/point.rs
new file mode 100644
index 00000000..31b84d81
--- /dev/null
+++ b/src/geom/point.rs
@@ -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);
diff --git a/src/geom/relative.rs b/src/geom/relative.rs
new file mode 100644
index 00000000..037c83dc
--- /dev/null
+++ b/src/geom/relative.rs
@@ -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);
diff --git a/src/geom/sides.rs b/src/geom/sides.rs
new file mode 100644
index 00000000..770fad58
--- /dev/null
+++ b/src/geom/sides.rs
@@ -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,
+ }
+ }
+}
diff --git a/src/geom/size.rs b/src/geom/size.rs
new file mode 100644
index 00000000..8a3951f7
--- /dev/null
+++ b/src/geom/size.rs
@@ -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);
diff --git a/src/geom/spec.rs b/src/geom/spec.rs
new file mode 100644
index 00000000..8a9519bc
--- /dev/null
+++ b/src/geom/spec.rs
@@ -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",
+ })
+ }
+}
diff --git a/src/layout/nodes/document.rs b/src/layout/document.rs
index 5c7a2410..c2d7b38b 100644
--- a/src/layout/nodes/document.rs
+++ b/src/layout/document.rs
@@ -1,6 +1,6 @@
use super::*;
-/// The top-level layouting node.
+/// The top-level layout node.
#[derive(Debug, Clone, PartialEq)]
pub struct Document {
pub runs: Vec<Pages>,
diff --git a/src/layout/nodes/fixed.rs b/src/layout/fixed.rs
index 0d438879..0d438879 100644
--- a/src/layout/nodes/fixed.rs
+++ b/src/layout/fixed.rs
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index bfd633d8..2368c441 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -1,17 +1,28 @@
//! Layouting of documents.
-pub mod nodes;
-pub mod primitive;
-
-pub use primitive::*;
+mod document;
+mod fixed;
+mod node;
+mod pad;
+mod par;
+mod spacing;
+mod stack;
+mod text;
use async_trait::async_trait;
use crate::font::SharedFontLoader;
-use crate::geom::{Point, Rect, Size, SizeExt};
+use crate::geom::*;
use crate::shaping::Shaped;
-use nodes::Document;
+pub use document::*;
+pub use fixed::*;
+pub use node::*;
+pub use pad::*;
+pub use par::*;
+pub use spacing::*;
+pub use stack::*;
+pub use text::*;
/// Layout a document and return the produced layouts.
pub async fn layout(document: &Document, loader: SharedFontLoader) -> Vec<BoxLayout> {
@@ -53,9 +64,9 @@ pub trait Layout {
#[derive(Debug, Clone, PartialEq)]
pub enum LayoutItem {
/// Spacing that should be added to the parent.
- Spacing(f64),
+ Spacing(Length),
/// A box that should be aligned in the parent.
- Box(BoxLayout, Gen2<GenAlign>),
+ Box(BoxLayout, Gen<Align>),
}
/// The constraints for layouting a single node.
@@ -101,7 +112,7 @@ impl BoxLayout {
/// given position.
pub fn push_layout(&mut self, pos: Point, more: Self) {
for (subpos, element) in more.elements {
- self.push(pos + subpos.to_vec2(), element);
+ self.push(pos + subpos, element);
}
}
}
diff --git a/src/layout/nodes/mod.rs b/src/layout/node.rs
index a304e63c..3230621f 100644
--- a/src/layout/nodes/mod.rs
+++ b/src/layout/node.rs
@@ -1,27 +1,9 @@
//! Layout nodes.
-mod document;
-mod fixed;
-mod pad;
-mod par;
-mod spacing;
-mod stack;
-mod text;
-
-pub use document::*;
-pub use fixed::*;
-pub use pad::*;
-pub use par::*;
-pub use spacing::*;
-pub use stack::*;
-pub use text::*;
-
use std::any::Any;
use std::fmt::{self, Debug, Formatter};
use std::ops::Deref;
-use async_trait::async_trait;
-
use super::*;
/// A self-contained, styled layout node.
diff --git a/src/layout/nodes/pad.rs b/src/layout/pad.rs
index 10a9e2c6..2e1817b7 100644
--- a/src/layout/nodes/pad.rs
+++ b/src/layout/pad.rs
@@ -21,8 +21,8 @@ impl Layout for Pad {
.spaces
.into_iter()
.map(|space| LayoutSpace {
- base: space.base + self.padding.insets(space.base).size(),
- size: space.size + self.padding.insets(space.size).size(),
+ base: space.base - self.padding.eval(space.base).size(),
+ size: space.size - self.padding.eval(space.size).size(),
})
.collect(),
repeat: constraints.repeat,
@@ -31,11 +31,11 @@ impl Layout for Pad {
.into_iter()
.map(|item| match item {
LayoutItem::Box(boxed, align) => {
- let padding = self.padding.insets(boxed.size);
- let padded = boxed.size - padding.size();
+ let padding = self.padding.eval(boxed.size);
+ let padded = boxed.size + padding.size();
let mut outer = BoxLayout::new(padded);
- let start = Point::new(-padding.x0, -padding.y0);
+ let start = Point::new(padding.left, padding.top);
outer.push_layout(start, boxed);
LayoutItem::Box(outer, align)
diff --git a/src/layout/nodes/par.rs b/src/layout/par.rs
index 082ab963..2a139760 100644
--- a/src/layout/nodes/par.rs
+++ b/src/layout/par.rs
@@ -7,11 +7,11 @@ use super::*;
/// the main axis by the height of the previous line plus extra line spacing.
#[derive(Debug, Clone, PartialEq)]
pub struct Par {
- pub dirs: Gen2<Dir>,
- pub line_spacing: f64,
+ pub dirs: Gen<Dir>,
+ pub line_spacing: Length,
pub children: Vec<LayoutNode>,
- pub aligns: Gen2<GenAlign>,
- pub expand: Spec2<bool>,
+ pub aligns: Gen<Align>,
+ pub expand: Spec<bool>,
}
#[async_trait(?Send)]
@@ -73,17 +73,17 @@ struct LineLayouter {
#[derive(Debug, Clone)]
struct LineContext {
/// The layout directions.
- dirs: Gen2<Dir>,
+ dirs: Gen<Dir>,
/// The spaces to layout into.
spaces: Vec<LayoutSpace>,
/// Whether to spill over into copies of the last space or finish layouting
/// when the last space is used up.
repeat: bool,
/// The spacing to be inserted between each pair of lines.
- line_spacing: f64,
+ line_spacing: Length,
/// Whether to expand the size of the resulting layout to the full size of
/// this space or to shrink it to fit the content.
- expand: Spec2<bool>,
+ expand: Spec<bool>,
}
impl LineLayouter {
@@ -102,7 +102,7 @@ impl LineLayouter {
}
/// Add a layout.
- fn push_box(&mut self, layout: BoxLayout, aligns: Gen2<GenAlign>) {
+ fn push_box(&mut self, layout: BoxLayout, aligns: Gen<Align>) {
let dirs = self.ctx.dirs;
if let Some(prev) = self.run.aligns {
if aligns.main != prev.main {
@@ -124,9 +124,9 @@ impl LineLayouter {
// FIXME: Alignment in non-expanding parent.
rest_run.usable = Some(match aligns.cross {
- GenAlign::Start => unreachable!("start > x"),
- GenAlign::Center => usable - 2.0 * self.run.size.cross,
- GenAlign::End => usable - self.run.size.cross,
+ Align::Start => unreachable!("start > x"),
+ Align::Center => usable - 2.0 * self.run.size.cross,
+ Align::End => usable - self.run.size.cross,
});
self.finish_line();
@@ -160,7 +160,7 @@ impl LineLayouter {
}
/// Add spacing to the line.
- fn push_spacing(&mut self, mut spacing: f64) {
+ fn push_spacing(&mut self, mut spacing: Length) {
spacing = spacing.min(self.usable().cross);
self.run.size.cross += spacing;
}
@@ -169,7 +169,7 @@ impl LineLayouter {
///
/// This specifies how much more would fit before a line break would be
/// needed.
- fn usable(&self) -> Gen2<f64> {
+ fn usable(&self) -> Gen<Length> {
// The base is the usable space of the stack layouter.
let mut usable = self.stack.usable().switch(self.ctx.dirs);
@@ -192,7 +192,7 @@ impl LineLayouter {
/// Whether the currently set line is empty.
fn line_is_empty(&self) -> bool {
- self.run.size == Gen2::ZERO && self.run.layouts.is_empty()
+ self.run.size == Gen::ZERO && self.run.layouts.is_empty()
}
/// Finish everything up and return the final collection of boxes.
@@ -224,7 +224,7 @@ impl LineLayouter {
self.run.size.cross - offset - child.size.get(dirs.cross.axis())
};
- let pos = Gen2::new(0.0, cross).switch(dirs).to_point();
+ let pos = Gen::new(Length::ZERO, cross).switch(dirs).to_point();
layout.push_layout(pos, child);
}
@@ -244,25 +244,25 @@ impl LineLayouter {
/// multiple runs with different alignments.
struct LineRun {
/// The so-far accumulated items of the run.
- layouts: Vec<(f64, BoxLayout)>,
+ layouts: Vec<(Length, BoxLayout)>,
/// The summed width and maximal height of the run.
- size: Gen2<f64>,
+ size: Gen<Length>,
/// The alignment of all layouts in the line.
///
/// When a new run is created the alignment is yet to be determined and
/// `None` as such. Once a layout is added, its alignment decides the
/// alignment for the whole run.
- aligns: Option<Gen2<GenAlign>>,
+ aligns: Option<Gen<Align>>,
/// The amount of cross-space left by another run on the same line or `None`
/// if this is the only run so far.
- usable: Option<f64>,
+ usable: Option<Length>,
}
impl LineRun {
fn new() -> Self {
Self {
layouts: vec![],
- size: Gen2::ZERO,
+ size: Gen::ZERO,
aligns: None,
usable: None,
}
@@ -283,7 +283,7 @@ pub(super) struct StackLayouter {
#[derive(Debug, Clone)]
pub(super) struct StackContext {
/// The layouting directions.
- pub dirs: Gen2<Dir>,
+ pub dirs: Gen<Dir>,
/// The spaces to layout into.
pub spaces: Vec<LayoutSpace>,
/// Whether to spill over into copies of the last space or finish layouting
@@ -291,7 +291,7 @@ pub(super) struct StackContext {
pub repeat: bool,
/// Whether to expand the size of the resulting layout to the full size of
/// this space or to shrink it to fit the content.
- pub expand: Spec2<bool>,
+ pub expand: Spec<bool>,
}
impl StackLayouter {
@@ -306,7 +306,7 @@ impl StackLayouter {
}
/// Add a layout to the stack.
- pub fn push_box(&mut self, layout: BoxLayout, aligns: Gen2<GenAlign>) {
+ pub fn push_box(&mut self, layout: BoxLayout, aligns: Gen<Align>) {
// If the alignment cannot be fitted in this space, finish it.
//
// TODO: Issue warning for non-fitting alignment in non-repeating
@@ -331,20 +331,20 @@ impl StackLayouter {
}
/// Add spacing to the stack.
- pub fn push_spacing(&mut self, mut spacing: f64) {
+ pub fn push_spacing(&mut self, mut spacing: Length) {
// Reduce the spacing such that it definitely fits.
let axis = self.ctx.dirs.main.axis();
spacing = spacing.min(self.space.usable.get(axis));
- let size = Gen2::new(spacing, 0.0);
+ let size = Gen::new(spacing, Length::ZERO);
self.update_metrics(size);
self.space.layouts.push((
BoxLayout::new(size.switch(self.ctx.dirs).to_size()),
- Gen2::default(),
+ Gen::default(),
));
}
- fn update_metrics(&mut self, added: Gen2<f64>) {
+ fn update_metrics(&mut self, added: Gen<Length>) {
let mut used = self.space.used.switch(self.ctx.dirs);
used.cross = used.cross.max(added.cross);
used.main += added.main;
@@ -398,11 +398,7 @@ impl StackLayouter {
/// Finish active current space and start a new one.
pub fn finish_space(&mut self, hard: bool) {
let dirs = self.ctx.dirs;
-
- // ------------------------------------------------------------------ //
- // Step 1: Determine the full size of the space.
- // (Mostly done already while collecting the boxes, but here we
- // expand if necessary.)
+ let main = dirs.main.axis();
let space = self.ctx.spaces[self.space.index];
let layout_size = {
@@ -416,64 +412,44 @@ impl StackLayouter {
used_size
};
- let mut layout = BoxLayout::new(layout_size);
+ let mut sum = Length::ZERO;
+ let mut sums = Vec::with_capacity(self.space.layouts.len() + 1);
- // ------------------------------------------------------------------ //
- // 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());
+ for (boxed, _) in &self.space.layouts {
+ sums.push(sum);
+ sum += boxed.size.get(main);
}
- // ------------------------------------------------------------------ //
- // 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;
+ sums.push(sum);
- // 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;
+ let mut layout = BoxLayout::new(layout_size);
+ let used = layout_size.switch(dirs);
- // And then, include this layout's main-axis extent.
- main_extent += layout.size.get(dirs.main.axis());
- }
+ let children = std::mem::take(&mut self.space.layouts);
+ for (i, (boxed, aligns)) in children.into_iter().enumerate() {
+ let size = boxed.size.switch(dirs);
+
+ let before = sums[i];
+ let after = sum - sums[i + 1];
+ let main_len = used.main - size.main;
+ let main_range = if dirs.main.is_positive() {
+ before .. main_len - after
+ } else {
+ main_len - before .. after
+ };
- // ------------------------------------------------------------------ //
- // Step 4: Align each layout in its bounding box and collect everything
- // into a single finished layout.
+ 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 children = std::mem::take(&mut self.space.layouts);
- for ((child, aligns), bound) in children.into_iter().zip(bounds) {
- // Align the child in its own bounds.
- let local =
- bound.size().anchor(dirs, aligns) - child.size.anchor(dirs, aligns);
+ let main = aligns.main.apply(main_range);
+ let cross = aligns.cross.apply(cross_range);
+ let pos = Gen::new(main, cross).switch(dirs).to_point();
- // Make the local position in the bounds global.
- let pos = bound.origin() + local;
- layout.push_layout(pos, child);
+ layout.push_layout(pos, boxed);
}
self.layouts.push(layout);
@@ -503,7 +479,7 @@ pub(super) struct Space {
/// Whether to include a layout for this space even if it would be empty.
hard: bool,
/// The so-far accumulated layouts.
- layouts: Vec<(BoxLayout, Gen2<GenAlign>)>,
+ layouts: Vec<(BoxLayout, Gen<Align>)>,
/// The full size of this space.
size: Size,
/// The used size of this space.
@@ -511,7 +487,7 @@ pub(super) struct Space {
/// The remaining space.
usable: Size,
/// Which alignments for new boxes are still allowed.
- pub(super) allowed_align: GenAlign,
+ pub(super) allowed_align: Align,
}
impl Space {
@@ -523,7 +499,7 @@ impl Space {
size,
used: Size::ZERO,
usable: size,
- allowed_align: GenAlign::Start,
+ allowed_align: Align::Start,
}
}
}
diff --git a/src/layout/primitive.rs b/src/layout/primitive.rs
deleted file mode 100644
index 30bd9363..00000000
--- a/src/layout/primitive.rs
+++ /dev/null
@@ -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,
- }
- }
-}
diff --git a/src/layout/nodes/spacing.rs b/src/layout/spacing.rs
index 9d72f7ca..9eac1ad5 100644
--- a/src/layout/nodes/spacing.rs
+++ b/src/layout/spacing.rs
@@ -5,7 +5,7 @@ use super::*;
/// A node that inserts spacing.
#[derive(Copy, Clone, PartialEq)]
pub struct Spacing {
- pub amount: f64,
+ pub amount: Length,
pub softness: Softness,
}
diff --git a/src/layout/nodes/stack.rs b/src/layout/stack.rs
index cca64e62..6cbe03e3 100644
--- a/src/layout/nodes/stack.rs
+++ b/src/layout/stack.rs
@@ -24,10 +24,10 @@ use super::*;
/// sentence in the second box.
#[derive(Debug, Clone, PartialEq)]
pub struct Stack {
- pub dirs: Gen2<Dir>,
+ pub dirs: Gen<Dir>,
pub children: Vec<LayoutNode>,
- pub aligns: Gen2<GenAlign>,
- pub expand: Spec2<bool>,
+ pub aligns: Gen<Align>,
+ pub expand: Spec<bool>,
}
#[async_trait(?Send)]
@@ -90,17 +90,17 @@ impl Layout for Stack {
}
struct StackSpace {
- dirs: Gen2<Dir>,
- expand: Spec2<bool>,
- boxes: Vec<(BoxLayout, Gen2<GenAlign>)>,
+ dirs: Gen<Dir>,
+ expand: Spec<bool>,
+ boxes: Vec<(BoxLayout, Gen<Align>)>,
full_size: Size,
usable: Size,
used: Size,
- ruler: GenAlign,
+ ruler: Align,
}
impl StackSpace {
- fn new(dirs: Gen2<Dir>, expand: Spec2<bool>, size: Size) -> Self {
+ fn new(dirs: Gen<Dir>, expand: Spec<bool>, size: Size) -> Self {
Self {
dirs,
expand,
@@ -108,14 +108,14 @@ impl StackSpace {
full_size: size,
usable: size,
used: Size::ZERO,
- ruler: GenAlign::Start,
+ ruler: Align::Start,
}
}
fn push_box(
&mut self,
boxed: BoxLayout,
- aligns: Gen2<GenAlign>,
+ aligns: Gen<Align>,
) -> Result<(), BoxLayout> {
let main = self.dirs.main.axis();
let cross = self.dirs.cross.axis();
@@ -133,15 +133,15 @@ impl StackSpace {
Ok(())
}
- fn push_spacing(&mut self, spacing: f64) {
+ fn push_spacing(&mut self, spacing: Length) {
let main = self.dirs.main.axis();
let max = self.usable.get(main);
let trimmed = spacing.min(max);
*self.used.get_mut(main) += trimmed;
*self.usable.get_mut(main) -= trimmed;
- let size = Gen2::new(trimmed, 0.0).switch(self.dirs);
- self.boxes.push((BoxLayout::new(size.to_size()), Gen2::default()));
+ let size = Gen::new(trimmed, Length::ZERO).switch(self.dirs);
+ self.boxes.push((BoxLayout::new(size.to_size()), Gen::default()));
}
fn finish(mut self) -> BoxLayout {
@@ -156,7 +156,7 @@ impl StackSpace {
self.used.height = self.full_size.height;
}
- let mut sum = 0.0;
+ let mut sum = Length::ZERO;
let mut sums = Vec::with_capacity(self.boxes.len() + 1);
for (boxed, _) in &self.boxes {
@@ -183,14 +183,14 @@ impl StackSpace {
let cross_len = used.cross - size.cross;
let cross_range = if dirs.cross.is_positive() {
- 0.0 .. cross_len
+ Length::ZERO .. cross_len
} else {
- cross_len .. 0.0
+ cross_len .. Length::ZERO
};
let main = aligns.main.apply(main_range);
let cross = aligns.cross.apply(cross_range);
- let pos = Gen2::new(main, cross).switch(dirs).to_point();
+ let pos = Gen::new(main, cross).switch(dirs).to_point();
layout.push_layout(pos, boxed);
}
diff --git a/src/layout/nodes/text.rs b/src/layout/text.rs
index b0c4a458..fafc1b14 100644
--- a/src/layout/nodes/text.rs
+++ b/src/layout/text.rs
@@ -10,11 +10,11 @@ use crate::shaping;
#[derive(Clone, PartialEq)]
pub struct Text {
pub text: String,
- pub size: f64,
+ pub size: Length,
pub dir: Dir,
pub fallback: Rc<FallbackTree>,
pub variant: FontVariant,
- pub aligns: Gen2<GenAlign>,
+ pub aligns: Gen<Align>,
}
#[async_trait(?Send)]
diff --git a/src/length.rs b/src/length.rs
deleted file mode 100644
index 437c741d..00000000
--- a/src/length.rs
+++ /dev/null
@@ -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);
- }
-}
diff --git a/src/lib.rs b/src/lib.rs
index 22e3b988..381270c9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -30,13 +30,13 @@
#[macro_use]
pub mod diag;
-pub mod color;
+#[macro_use]
pub mod eval;
+pub mod color;
pub mod export;
pub mod font;
pub mod geom;
pub mod layout;
-pub mod length;
pub mod library;
pub mod paper;
pub mod parse;
diff --git a/src/library/align.rs b/src/library/align.rs
index acd3a85c..d6b14692 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -1,4 +1,5 @@
use crate::prelude::*;
+use std::fmt::{self, Display, Formatter};
/// `align`: Align content along the layouting axes.
///
@@ -18,10 +19,10 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value {
let snapshot = ctx.state.clone();
let body = args.find::<SynTree>();
- let first = args.get::<_, Spanned<SpecAlign>>(ctx, 0);
- let second = args.get::<_, Spanned<SpecAlign>>(ctx, 1);
- let hor = args.get::<_, Spanned<SpecAlign>>(ctx, "horizontal");
- let ver = args.get::<_, Spanned<SpecAlign>>(ctx, "vertical");
+ let first = args.get::<_, Spanned<AlignArg>>(ctx, 0);
+ let second = args.get::<_, Spanned<AlignArg>>(ctx, 1);
+ let hor = args.get::<_, Spanned<AlignArg>>(ctx, "horizontal");
+ let ver = args.get::<_, Spanned<AlignArg>>(ctx, "vertical");
args.done(ctx);
let iter = first
@@ -50,10 +51,10 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value {
/// Deduplicate alignments and deduce to which axes they apply.
fn dedup_aligns(
ctx: &mut EvalContext,
- iter: impl Iterator<Item = (Option<SpecAxis>, Spanned<SpecAlign>)>,
-) -> Gen2<GenAlign> {
+ iter: impl Iterator<Item = (Option<SpecAxis>, Spanned<AlignArg>)>,
+) -> Gen<Align> {
let mut aligns = ctx.state.aligns;
- let mut had = Gen2::new(false, false);
+ let mut had = Gen::new(false, false);
let mut had_center = false;
for (axis, Spanned { v: align, span }) in iter {
@@ -77,15 +78,15 @@ fn dedup_aligns(
} else {
// We don't know the axis: This has to be a `center` alignment for a
// positional argument.
- debug_assert_eq!(align, SpecAlign::Center);
+ debug_assert_eq!(align, AlignArg::Center);
if had.main && had.cross {
ctx.diag(error!(span, "duplicate alignment"));
} else if had_center {
// Both this and the previous one are unspecified `center`
// alignments. Both axes should be centered.
- aligns = Gen2::new(GenAlign::Center, GenAlign::Center);
- had = Gen2::new(true, true);
+ aligns = Gen::new(Align::Center, Align::Center);
+ had = Gen::new(true, true);
} else {
had_center = true;
}
@@ -95,10 +96,10 @@ fn dedup_aligns(
// alignment.
if had_center && (had.main || had.cross) {
if had.main {
- aligns.cross = GenAlign::Center;
+ aligns.cross = Align::Center;
had.cross = true;
} else {
- aligns.main = GenAlign::Center;
+ aligns.main = Align::Center;
had.main = true;
}
had_center = false;
@@ -108,8 +109,77 @@ fn dedup_aligns(
// If center has not been flushed by now, it is the only argument and then
// we default to applying it to the cross axis.
if had_center {
- aligns.cross = GenAlign::Center;
+ aligns.cross = Align::Center;
}
aligns
}
+
+/// An alignment argument.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
+enum AlignArg {
+ Left,
+ Right,
+ Top,
+ Bottom,
+ Center,
+}
+
+impl AlignArg {
+ /// The specific axis this alignment refers to.
+ ///
+ /// Returns `None` if this is `Center` since the axis is unknown.
+ pub fn axis(self) -> Option<SpecAxis> {
+ match self {
+ Self::Left => Some(SpecAxis::Horizontal),
+ Self::Right => Some(SpecAxis::Horizontal),
+ Self::Top => Some(SpecAxis::Vertical),
+ Self::Bottom => Some(SpecAxis::Vertical),
+ Self::Center => None,
+ }
+ }
+}
+
+impl Switch for AlignArg {
+ type Other = Align;
+
+ fn switch(self, dirs: Gen<Dir>) -> Self::Other {
+ let get = |dir: Dir, at_positive_start| {
+ if dir.is_positive() == at_positive_start {
+ Align::Start
+ } else {
+ Align::End
+ }
+ };
+
+ let dirs = dirs.switch(dirs);
+ match self {
+ Self::Left => get(dirs.horizontal, true),
+ Self::Right => get(dirs.horizontal, false),
+ Self::Top => get(dirs.vertical, true),
+ Self::Bottom => get(dirs.vertical, false),
+ Self::Center => Align::Center,
+ }
+ }
+}
+
+convert_ident!(AlignArg, "alignment", |v| match v {
+ "left" => Some(Self::Left),
+ "right" => Some(Self::Right),
+ "top" => Some(Self::Top),
+ "bottom" => Some(Self::Bottom),
+ "center" => Some(Self::Center),
+ _ => None,
+});
+
+impl Display for AlignArg {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ Self::Left => "left",
+ Self::Right => "right",
+ Self::Top => "top",
+ Self::Bottom => "bottom",
+ Self::Center => "center",
+ })
+ }
+}
diff --git a/src/library/boxed.rs b/src/library/boxed.rs
index 6edb3b17..0c1ed30a 100644
--- a/src/library/boxed.rs
+++ b/src/library/boxed.rs
@@ -1,5 +1,5 @@
use crate::geom::Linear;
-use crate::layout::nodes::{Fixed, Stack};
+use crate::layout::{Fixed, Stack};
use crate::prelude::*;
/// `box`: Layouts its contents into a box.
@@ -33,7 +33,7 @@ pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value {
dirs,
children,
aligns,
- expand: Spec2::new(width.is_some(), height.is_some()),
+ expand: Spec::new(width.is_some(), height.is_some()),
}),
});
diff --git a/src/library/font.rs b/src/library/font.rs
index be6823c3..e6d9cd12 100644
--- a/src/library/font.rs
+++ b/src/library/font.rs
@@ -57,9 +57,9 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value {
let body = args.find::<SynTree>();
if let Some(linear) = args.find::<Linear>() {
- if linear.rel == 0.0 {
+ if linear.is_absolute() {
ctx.state.text.font_size.base = linear.abs;
- ctx.state.text.font_size.scale = Linear::rel(1.0);
+ ctx.state.text.font_size.scale = Relative::ONE.into();
} else {
ctx.state.text.font_size.scale = linear;
}
diff --git a/src/library/page.rs b/src/library/page.rs
index 3c6703bc..570dbb10 100644
--- a/src/library/page.rs
+++ b/src/library/page.rs
@@ -1,7 +1,6 @@
use std::mem;
-use crate::eval::Absolute;
-use crate::geom::Linear;
+use crate::geom::{Length, Linear};
use crate::paper::{Paper, PaperClass};
use crate::prelude::*;
@@ -25,12 +24,12 @@ pub fn page(mut args: Args, ctx: &mut EvalContext) -> Value {
ctx.state.page.size = paper.size();
}
- if let Some(Absolute(width)) = args.get::<_, Absolute>(ctx, "width") {
+ if let Some(width) = args.get::<_, Length>(ctx, "width") {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.width = width;
}
- if let Some(Absolute(height)) = args.get::<_, Absolute>(ctx, "height") {
+ if let Some(height) = args.get::<_, Length>(ctx, "height") {
ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.height = height;
}
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
index 6d00bd1c..a2b21e93 100644
--- a/src/library/spacing.rs
+++ b/src/library/spacing.rs
@@ -1,5 +1,5 @@
use crate::geom::Linear;
-use crate::layout::nodes::{Softness, Spacing};
+use crate::layout::{Softness, Spacing};
use crate::prelude::*;
/// `h`: Add horizontal spacing.
diff --git a/src/paper.rs b/src/paper.rs
index 21762b17..21f54756 100644
--- a/src/paper.rs
+++ b/src/paper.rs
@@ -1,18 +1,16 @@
//! Predefined papers.
-use crate::geom::{Linear, Size};
-use crate::layout::Sides;
-use crate::length::Length;
+use crate::geom::{Length, Linear, Relative, Sides, Size};
/// Specification of a paper.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Paper {
/// The kind of paper, which defines the default margins.
pub class: PaperClass,
- /// The width of the paper.
- pub width: Length,
- /// The height of the paper.
- pub height: Length,
+ /// The width of the paper in millimeters.
+ pub width: f64,
+ /// The height of the paper in millimeters.
+ pub height: f64,
}
impl Paper {
@@ -23,7 +21,7 @@ impl Paper {
/// The size of the paper.
pub fn size(self) -> Size {
- Size::new(self.width.as_raw(), self.height.as_raw())
+ Size::new(Length::mm(self.width), Length::mm(self.height))
}
}
@@ -40,7 +38,7 @@ pub enum PaperClass {
impl PaperClass {
/// The default margins for this page class.
pub fn default_margins(self) -> Sides<Linear> {
- let f = Linear::rel;
+ let f = |r| Relative::new(r).into();
let s = |l, r, t, b| Sides::new(f(l), f(r), f(t), f(b));
match self {
Self::Custom => s(0.1190, 0.0842, 0.1190, 0.0842),
@@ -69,9 +67,9 @@ macro_rules! papers {
#[doc = $names]
#[doc = "`."]
pub const $var: Paper = Paper {
- width: Length::mm($width),
- height: Length::mm($height),
class: PaperClass::$class,
+ width: $width,
+ height: $height,
};
};
}
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index 3d980d7c..90fdbf5d 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -432,7 +432,7 @@ fn value(p: &mut Parser) -> Option<Expr> {
Token::Bool(b) => Expr::Lit(Lit::Bool(b)),
Token::Int(i) => Expr::Lit(Lit::Int(i)),
Token::Float(f) => Expr::Lit(Lit::Float(f)),
- Token::Length(l) => Expr::Lit(Lit::Length(l)),
+ Token::Length(val, unit) => Expr::Lit(Lit::Length(val, unit)),
Token::Percent(p) => Expr::Lit(Lit::Percent(p)),
Token::Hex(hex) => Expr::Lit(Lit::Color(color(p, hex, start))),
Token::Str(token) => Expr::Lit(Lit::Str(string(p, token))),
diff --git a/src/parse/tests.rs b/src/parse/tests.rs
index d76c6dca..e0712b36 100644
--- a/src/parse/tests.rs
+++ b/src/parse/tests.rs
@@ -8,7 +8,7 @@ use super::parse;
use crate::color::RgbaColor;
use crate::diag::Deco;
use crate::eval::DictKey;
-use crate::length::Length;
+use crate::geom::Unit;
use crate::syntax::*;
// ------------------------------ Construct Syntax Nodes ------------------------------ //
@@ -51,6 +51,7 @@ macro_rules! F {
use BinOp::*;
use UnOp::*;
+use Unit::*;
fn Id(ident: &str) -> Expr {
Expr::Lit(Lit::Ident(Ident(ident.to_string())))
@@ -67,8 +68,8 @@ fn Float(float: f64) -> Expr {
fn Percent(percent: f64) -> Expr {
Expr::Lit(Lit::Percent(percent))
}
-fn Len(length: Length) -> Expr {
- Expr::Lit(Lit::Length(length))
+fn Length(val: f64, unit: Unit) -> Expr {
+ Expr::Lit(Lit::Length(val, unit))
}
fn Color(color: RgbaColor) -> Expr {
Expr::Lit(Lit::Color(color))
@@ -347,10 +348,10 @@ fn test_parse_chaining() {
// Things the parser has to make sense of
t!("[hi: (5.0, 2.1 >> you]" => F!("hi"; Dict![Float(5.0), Float(2.1)], Tree![F!("you")]));
t!("[box >> pad: 1pt][Hi]" => F!("box"; Tree![
- F!("pad"; Len(Length::pt(1.0)), Tree!(T("Hi")))
+ F!("pad"; Length(1.0, Pt), Tree!(T("Hi")))
]));
t!("[bold: 400, >> emph >> sub: 1cm]" => F!("bold"; Int(400), Tree![
- F!("emph"; Tree!(F!("sub"; Len(Length::cm(1.0)))))
+ F!("emph"; Tree!(F!("sub"; Length(1.0, Cm))))
]));
// Errors for unclosed / empty predecessor groups
@@ -411,8 +412,8 @@ fn test_parse_values() {
v!("1.0e-4" => Float(1e-4));
v!("3.15" => Float(3.15));
v!("50%" => Percent(50.0));
- v!("4.5cm" => Len(Length::cm(4.5)));
- v!("12e1pt" => Len(Length::pt(12e1)));
+ v!("4.5cm" => Length(4.5, Cm));
+ v!("12e1pt" => Length(12e1, Pt));
v!("#f7a20500" => Color(RgbaColor::new(0xf7, 0xa2, 0x05, 0x00)));
v!("\"a\n[]\\\"string\"" => Str("a\n[]\"string"));
@@ -446,15 +447,15 @@ fn test_parse_expressions() {
// Operations.
v!("-1" => Unary(Neg, Int(1)));
v!("-- 1" => Unary(Neg, Unary(Neg, Int(1))));
- v!("3.2in + 6pt" => Binary(Add, Len(Length::inches(3.2)), Len(Length::pt(6.0))));
+ v!("3.2in + 6pt" => Binary(Add, Length(3.2, In), Length(6.0, Pt)));
v!("5 - 0.01" => Binary(Sub, Int(5), Float(0.01)));
- v!("(3mm * 2)" => Binary(Mul, Len(Length::mm(3.0)), Int(2)));
- v!("12e-3cm/1pt" => Binary(Div, Len(Length::cm(12e-3)), Len(Length::pt(1.0))));
+ v!("(3mm * 2)" => Binary(Mul, Length(3.0, Mm), Int(2)));
+ v!("12e-3cm/1pt" => Binary(Div, Length(12e-3, Cm), Length(1.0, Pt)));
// More complex.
v!("(3.2in + 6pt)*(5/2-1)" => Binary(
Mul,
- Binary(Add, Len(Length::inches(3.2)), Len(Length::pt(6.0))),
+ Binary(Add, Length(3.2, In), Length(6.0, Pt)),
Binary(Sub, Binary(Div, Int(5), Int(2)), Int(1))
));
v!("(6.3E+2+4* - 3.2pt)/2" => Binary(
@@ -462,7 +463,7 @@ fn test_parse_expressions() {
Binary(Add, Float(6.3e2), Binary(
Mul,
Int(4),
- Unary(Neg, Len(Length::pt(3.2)))
+ Unary(Neg, Length(3.2, Pt))
)),
Int(2)
));
@@ -483,11 +484,11 @@ fn test_parse_expressions() {
ts!("[val: (1)]" => s(0, 10, F!(s(1, 4, "val"), 5 .. 9; s(6, 9, Int(1)))));
// Invalid expressions.
- v!("4pt--" => Len(Length::pt(4.0)));
+ v!("4pt--" => Length(4.0, Pt));
e!("[val: 4pt--]" => s(10, 11, "missing factor"),
s(6, 10, "missing right summand"));
- v!("3mm+4pt*" => Binary(Add, Len(Length::mm(3.0)), Len(Length::pt(4.0))));
+ v!("3mm+4pt*" => Binary(Add, Length(3.0, Mm), Length(4.0, Pt)));
e!("[val: 3mm+4pt*]" => s(10, 14, "missing right factor"));
}
@@ -525,7 +526,7 @@ fn test_parse_dicts_compute_func_calls() {
// More complex.
v!("css(1pt, rgb(90, 102, 254), \"solid\")" => Call!(
"css";
- Len(Length::pt(1.0)),
+ Length(1.0, Pt),
Call!("rgb"; Int(90), Int(102), Int(254)),
Str("solid"),
));
@@ -546,7 +547,7 @@ fn test_parse_dicts_nested() {
Int(1),
Dict!(
"ab" => Dict![],
- "d" => Dict!(Int(3), Len(Length::pt(14.0))),
+ "d" => Dict!(Int(3), Length(14.0, Pt)),
),
],
Bool(false),
diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs
index 2ff580b6..f53ab586 100644
--- a/src/parse/tokens.rs
+++ b/src/parse/tokens.rs
@@ -3,7 +3,7 @@
use std::fmt::{self, Debug, Formatter};
use super::{is_newline, Scanner};
-use crate::length::Length;
+use crate::geom::Unit;
use crate::syntax::token::*;
use crate::syntax::{is_ident, Pos};
@@ -279,8 +279,8 @@ fn parse_expr(text: &str) -> Token<'_> {
Token::Float(num)
} else if let Some(percent) = parse_percent(text) {
Token::Percent(percent)
- } else if let Ok(length) = text.parse::<Length>() {
- Token::Length(length)
+ } else if let Some((val, unit)) = parse_length(text) {
+ Token::Length(val, unit)
} else if is_ident(text) {
Token::Ident(text)
} else {
@@ -292,19 +292,41 @@ fn parse_percent(text: &str) -> Option<f64> {
text.strip_suffix('%').and_then(|num| num.parse::<f64>().ok())
}
+fn parse_length(text: &str) -> Option<(f64, Unit)> {
+ let len = text.len();
+
+ // We need at least some number and the unit.
+ if len <= 2 {
+ return None;
+ }
+
+ // We can view the string as bytes since a multibyte UTF-8 char cannot
+ // have valid ASCII chars as subbytes.
+ let split = len - 2;
+ let bytes = text.as_bytes();
+ let unit = match &bytes[split ..] {
+ b"pt" => Unit::Pt,
+ b"mm" => Unit::Mm,
+ b"cm" => Unit::Cm,
+ b"in" => Unit::In,
+ _ => return None,
+ };
+
+ text[.. split].parse::<f64>().ok().map(|val| (val, unit))
+}
+
#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
use super::*;
- use crate::length::Length;
use crate::parse::tests::check;
use Token::{
- BlockComment as BC, Bool, Chain, Float, Hex, Hyphen as Min, Ident as Id, Int,
- LeftBrace as LB, LeftBracket as L, LeftParen as LP, Length as Len,
- LineComment as LC, NonBreakingSpace as Nbsp, Percent, Plus, RightBrace as RB,
- RightBracket as R, RightParen as RP, Slash, Space as S, Star, Text as T, *,
+ BlockComment as BC, Hyphen as Min, Ident as Id, LeftBrace as LB,
+ LeftBracket as L, LeftParen as LP, LineComment as LC, NonBreakingSpace as Nbsp,
+ RightBrace as RB, RightBracket as R, RightParen as RP, Space as S, Text as T, *,
};
+ use Unit::*;
fn Str(string: &str, terminated: bool) -> Token {
Token::Str(TokenStr { string, terminated })
@@ -325,6 +347,16 @@ mod tests {
}
#[test]
+ fn test_length_from_str_parses_correct_value_and_unit() {
+ assert_eq!(parse_length("2.5cm"), Some((2.5, Cm)));
+ }
+
+ #[test]
+ fn test_length_from_str_works_with_non_ascii_chars() {
+ assert_eq!(parse_length("123🚚"), None);
+ }
+
+ #[test]
fn tokenize_whitespace() {
t!(Body, "" => );
t!(Body, " " => S(0));
@@ -429,7 +461,7 @@ mod tests {
t!(Header, "🌓, 🌍," => Invalid("🌓"), Comma, S(0), Invalid("🌍"), Comma);
t!(Header, "{abc}" => LB, Id("abc"), RB);
t!(Header, "(1,2)" => LP, Int(1), Comma, Int(2), RP);
- t!(Header, "12_pt, 12pt" => Invalid("12_pt"), Comma, S(0), Len(Length::pt(12.0)));
+ t!(Header, "12_pt, 12pt" => Invalid("12_pt"), Comma, S(0), Length(12.0, Pt));
t!(Header, "f: arg >> g" => Id("f"), Colon, S(0), Id("arg"), S(0), Chain, S(0), Id("g"));
t!(Header, "=3.15" => Equals, Float(3.15));
t!(Header, "arg, _b, _1" => Id("arg"), Comma, S(0), Id("_b"), Comma, S(0), Id("_1"));
@@ -446,9 +478,9 @@ mod tests {
t!(Header, "12.3e5" => Float(12.3e5));
t!(Header, "120%" => Percent(120.0));
t!(Header, "12e4%" => Percent(120000.0));
- t!(Header, "1e5in" => Len(Length::inches(100000.0)));
- t!(Header, "2.3cm" => Len(Length::cm(2.3)));
- t!(Header, "02.4mm" => Len(Length::mm(2.4)));
+ t!(Header, "1e5in" => Length(100000.0, In));
+ t!(Header, "2.3cm" => Length(2.3, Cm));
+ t!(Header, "02.4mm" => Length(2.4, Mm));
t!(Header, "2.4.cm" => Invalid("2.4.cm"));
t!(Header, "#6ae6dd" => Hex("6ae6dd"));
t!(Header, "#8A083c" => Hex("8A083c"));
@@ -469,11 +501,11 @@ mod tests {
#[test]
fn tokenize_math() {
- t!(Header, "12e-3in" => Len(Length::inches(12e-3)));
+ t!(Header, "12e-3in" => Length(12e-3, In));
t!(Header, "-1" => Min, Int(1));
t!(Header, "--1" => Min, Min, Int(1));
t!(Header, "- 1" => Min, S(0), Int(1));
- t!(Header, "6.1cm + 4pt,a=1*2" => Len(Length::cm(6.1)), S(0), Plus, S(0), Len(Length::pt(4.0)),
+ t!(Header, "6.1cm + 4pt,a=1*2" => Length(6.1, Cm), S(0), Plus, S(0), Length(4.0, Pt),
Comma, Id("a"), Equals, Int(1), Star, Int(2));
t!(Header, "(5 - 1) / 2.1" => LP, Int(5), S(0), Min, S(0), Int(1), RP,
S(0), Slash, S(0), Float(2.1));
diff --git a/src/prelude.rs b/src/prelude.rs
index 82da91f4..16b598ce 100644
--- a/src/prelude.rs
+++ b/src/prelude.rs
@@ -3,8 +3,9 @@
pub use crate::diag::{Feedback, Pass};
#[doc(no_inline)]
pub use crate::eval::{Args, Dict, Eval, EvalContext, Value, ValueDict};
-pub use crate::layout::nodes::LayoutNode;
-pub use crate::layout::primitive::*;
+pub use crate::geom::*;
+#[doc(no_inline)]
+pub use crate::layout::LayoutNode;
#[doc(no_inline)]
pub use crate::syntax::{Span, Spanned, SynTree};
pub use crate::{error, warning};
diff --git a/src/shaping.rs b/src/shaping.rs
index c620f872..bc6fb6b7 100644
--- a/src/shaping.rs
+++ b/src/shaping.rs
@@ -10,8 +10,8 @@ use fontdock::{FaceId, FaceQuery, FallbackTree, FontVariant};
use ttf_parser::{Face, GlyphId};
use crate::font::FontLoader;
-use crate::geom::{Point, Size};
-use crate::layout::{BoxLayout, Dir, LayoutElement};
+use crate::geom::{Dir, Length, Point, Size};
+use crate::layout::{BoxLayout, LayoutElement};
/// A shaped run of text.
#[derive(Clone, PartialEq)]
@@ -24,14 +24,14 @@ pub struct Shaped {
pub glyphs: Vec<GlyphId>,
/// The horizontal offsets of the glyphs. This is indexed parallel to `glyphs`.
/// Vertical offets are not yet supported.
- pub offsets: Vec<f64>,
+ pub offsets: Vec<Length>,
/// The font size.
- pub size: f64,
+ pub size: Length,
}
impl Shaped {
/// Create a new shape run with empty `text`, `glyphs` and `offsets`.
- pub fn new(face: FaceId, size: f64) -> Self {
+ pub fn new(face: FaceId, size: Length) -> Self {
Self {
text: String::new(),
face,
@@ -63,15 +63,15 @@ impl Debug for Shaped {
/// [`Shaped`]: struct.Shaped.html
pub async fn shape(
text: &str,
- size: f64,
+ size: Length,
dir: Dir,
loader: &mut FontLoader,
fallback: &FallbackTree,
variant: FontVariant,
) -> BoxLayout {
- let mut layout = BoxLayout::new(Size::new(0.0, size));
+ let mut layout = BoxLayout::new(Size::new(Length::ZERO, size));
let mut shaped = Shaped::new(FaceId::MAX, size);
- let mut offset = 0.0;
+ let mut offset = Length::ZERO;
// Create an iterator with conditional direction.
let mut forwards = text.chars();
@@ -93,11 +93,11 @@ pub async fn shape(
// Flush the buffer if we change the font face.
if shaped.face != id && !shaped.text.is_empty() {
- let pos = Point::new(layout.size.width, 0.0);
+ let pos = Point::new(layout.size.width, Length::ZERO);
layout.push(pos, LayoutElement::Text(shaped));
layout.size.width += offset;
shaped = Shaped::new(FaceId::MAX, size);
- offset = 0.0;
+ offset = Length::ZERO;
}
shaped.face = id;
@@ -110,7 +110,7 @@ pub async fn shape(
// Flush the last buffered parts of the word.
if !shaped.text.is_empty() {
- let pos = Point::new(layout.size.width, 0.0);
+ let pos = Point::new(layout.size.width, Length::ZERO);
layout.push(pos, LayoutElement::Text(shaped));
layout.size.width += offset;
}
@@ -120,7 +120,7 @@ pub async fn shape(
/// Looks up the glyph for `c` and returns its index alongside its width at the
/// given `size`.
-fn lookup_glyph(face: &Face, c: char, size: f64) -> Option<(GlyphId, f64)> {
+fn lookup_glyph(face: &Face, c: char, size: Length) -> Option<(GlyphId, Length)> {
let glyph = face.glyph_index(c)?;
// Determine the width of the char.
diff --git a/src/syntax/ast/lit.rs b/src/syntax/ast/lit.rs
index 4370345a..40b360da 100644
--- a/src/syntax/ast/lit.rs
+++ b/src/syntax/ast/lit.rs
@@ -3,7 +3,7 @@
use super::*;
use crate::color::RgbaColor;
use crate::eval::DictKey;
-use crate::length::Length;
+use crate::geom::Unit;
/// A literal.
#[derive(Debug, Clone, PartialEq)]
@@ -17,13 +17,13 @@ pub enum Lit {
/// A floating-point literal: `1.2`, `10e-4`.
Float(f64),
/// A length literal: `12pt`, `3cm`.
- Length(Length),
+ Length(f64, Unit),
/// A percent literal: `50%`.
///
- /// _Note_: `50%` is represented as `50.0` here, but as `0.5` in the
+ /// _Note_: `50%` is stored as `50.0` here, but as `0.5` in the
/// corresponding [value].
///
- /// [value]: ../../eval/enum.Value.html#variant.Relative
+ /// [value]: ../../geom/struct.Relative.html
Percent(f64),
/// A color literal: `#ffccee`.
Color(RgbaColor),
diff --git a/src/syntax/token.rs b/src/syntax/token.rs
index cb0526c1..5b055e39 100644
--- a/src/syntax/token.rs
+++ b/src/syntax/token.rs
@@ -1,6 +1,6 @@
//! Token definition.
-use crate::length::Length;
+use crate::geom::Unit;
/// A minimal semantic entity of source code.
#[derive(Debug, Copy, Clone, PartialEq)]
@@ -72,10 +72,10 @@ pub enum Token<'s> {
/// A floating-point number: `1.2`, `10e-4`.
Float(f64),
/// A length: `12pt`, `3cm`.
- Length(Length),
+ Length(f64, Unit),
/// A percentage: `50%`.
///
- /// _Note_: `50%` is represented as `50.0` here, as in the corresponding
+ /// _Note_: `50%` is stored as `50.0` here, as in the corresponding
/// [literal].
///
/// [literal]: ../ast/enum.Lit.html#variant.Percent
@@ -159,7 +159,7 @@ impl<'s> Token<'s> {
Self::Bool(_) => "bool",
Self::Int(_) => "integer",
Self::Float(_) => "float",
- Self::Length(_) => "length",
+ Self::Length(..) => "length",
Self::Percent(_) => "percentage",
Self::Hex(_) => "hex value",
Self::Str { .. } => "string",
diff --git a/tests/test_typeset.rs b/tests/test_typeset.rs
index 52f7a1fa..3789d978 100644
--- a/tests/test_typeset.rs
+++ b/tests/test_typeset.rs
@@ -15,7 +15,7 @@ use typstc::diag::{Feedback, Pass};
use typstc::eval::State;
use typstc::export::pdf;
use typstc::font::{FontLoader, SharedFontLoader};
-use typstc::geom::{Point, Vec2};
+use typstc::geom::{Length, Point};
use typstc::layout::{BoxLayout, LayoutElement};
use typstc::parse::LineMap;
use typstc::shaping::Shaped;
@@ -138,43 +138,40 @@ impl TestFilter {
}
fn render(layouts: &[BoxLayout], loader: &FontLoader, scale: f64) -> DrawTarget {
- let pad = scale * 10.0;
+ let pad = Length::pt(scale * 10.0);
let width = 2.0 * pad
+ layouts
.iter()
.map(|l| scale * l.size.width)
.max_by(|a, b| a.partial_cmp(&b).unwrap())
- .unwrap()
- .round();
+ .unwrap();
- let height = pad
- + layouts
- .iter()
- .map(|l| scale * l.size.height + pad)
- .sum::<f64>()
- .round();
+ let height =
+ pad + layouts.iter().map(|l| scale * l.size.height + pad).sum::<Length>();
- let mut surface = DrawTarget::new(width as i32, height as i32);
+ let int_width = width.to_pt().round() as i32;
+ let int_height = height.to_pt().round() as i32;
+ let mut surface = DrawTarget::new(int_width, int_height);
surface.clear(BLACK);
- let mut offset = Vec2::new(pad, pad);
+ let mut offset = Point::new(pad, pad);
for layout in layouts {
surface.fill_rect(
- offset.x as f32,
- offset.y as f32,
- (scale * layout.size.width) as f32,
- (scale * layout.size.height) as f32,
+ offset.x.to_pt() as f32,
+ offset.y.to_pt() as f32,
+ (scale * layout.size.width).to_pt() as f32,
+ (scale * layout.size.height).to_pt() as f32,
&Source::Solid(WHITE),
&Default::default(),
);
- for (pos, element) in &layout.elements {
+ for &(pos, ref element) in &layout.elements {
match element {
LayoutElement::Text(shaped) => render_shaped(
&mut surface,
loader,
shaped,
- (scale * pos.to_vec2() + offset).to_point(),
+ scale * pos + offset,
scale,
),
}
@@ -205,8 +202,8 @@ fn render_shaped(
let x = pos.x + scale * offset;
let y = pos.y + scale * shaped.size;
- let t = Transform::create_scale(s as f32, -s as f32)
- .post_translate(Vector::new(x as f32, y as f32));
+ let t = Transform::create_scale(s.to_pt() as f32, -s.to_pt() as f32)
+ .post_translate(Vector::new(x.to_pt() as f32, y.to_pt() as f32));
surface.fill(
&path.transform(&t),