mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Switch back to custom geometry types, unified with layout primitives 🏞
This commit is contained in:
parent
42500d5ed8
commit
92c01da360
@ -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 }
|
||||
|
||||
|
@ -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>) {
|
||||
|
@ -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()));
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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`.
|
||||
|
@ -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());
|
||||
}
|
||||
|
235
src/geom.rs
235
src/geom.rs
@ -1,235 +0,0 @@
|
||||
//! Geometrical types.
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use kurbo::*;
|
||||
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::ops::*;
|
||||
|
||||
use crate::layout::{Dir, Gen2, GenAlign, Get, Side, Spec2, SpecAxis, Switch};
|
||||
|
||||
macro_rules! impl_2d {
|
||||
($t:ty, $x:ident, $y:ident) => {
|
||||
impl Get<SpecAxis> for $t {
|
||||
type Component = f64;
|
||||
|
||||
fn get(self, axis: SpecAxis) -> f64 {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => self.$x,
|
||||
SpecAxis::Vertical => self.$y,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, axis: SpecAxis) -> &mut f64 {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => &mut self.$x,
|
||||
SpecAxis::Vertical => &mut self.$y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Switch for $t {
|
||||
type Other = Gen2<f64>;
|
||||
|
||||
fn switch(self, dirs: Gen2<Dir>) -> Self::Other {
|
||||
Spec2::new(self.$x, self.$y).switch(dirs)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_2d!(Point, x, y);
|
||||
impl_2d!(Vec2, x, y);
|
||||
impl_2d!(Size, width, height);
|
||||
|
||||
impl Get<Side> for Rect {
|
||||
type Component = f64;
|
||||
|
||||
fn get(self, side: Side) -> f64 {
|
||||
match side {
|
||||
Side::Left => self.x0,
|
||||
Side::Top => self.y0,
|
||||
Side::Right => self.x1,
|
||||
Side::Bottom => self.y1,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, side: Side) -> &mut f64 {
|
||||
match side {
|
||||
Side::Left => &mut self.x0,
|
||||
Side::Top => &mut self.y0,
|
||||
Side::Right => &mut self.x1,
|
||||
Side::Bottom => &mut self.y1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods for [sizes].
|
||||
///
|
||||
/// [sizes]: ../../kurbo/struct.Size.html
|
||||
pub trait SizeExt {
|
||||
/// Whether the given size fits into this one, that is, both coordinate
|
||||
/// values are smaller or equal.
|
||||
fn fits(self, other: Self) -> bool;
|
||||
|
||||
/// The anchor position for an object to be aligned in a container with this
|
||||
/// size and the given directions.
|
||||
fn anchor(self, dirs: Gen2<Dir>, aligns: Gen2<GenAlign>) -> Point;
|
||||
}
|
||||
|
||||
impl SizeExt for Size {
|
||||
fn fits(self, other: Self) -> bool {
|
||||
self.width >= other.width && self.height >= other.height
|
||||
}
|
||||
|
||||
fn anchor(self, dirs: Gen2<Dir>, aligns: Gen2<GenAlign>) -> Point {
|
||||
fn anchor(length: f64, dir: Dir, align: GenAlign) -> f64 {
|
||||
match if dir.is_positive() { align } else { align.inv() } {
|
||||
GenAlign::Start => 0.0,
|
||||
GenAlign::Center => length / 2.0,
|
||||
GenAlign::End => length,
|
||||
}
|
||||
}
|
||||
|
||||
let switched = self.switch(dirs);
|
||||
let generic = Gen2::new(
|
||||
anchor(switched.main, dirs.main, aligns.main),
|
||||
anchor(switched.cross, dirs.cross, aligns.cross),
|
||||
);
|
||||
|
||||
generic.switch(dirs).to_point()
|
||||
}
|
||||
}
|
||||
|
||||
/// A function that depends linearly on one value.
|
||||
///
|
||||
/// This represents a function `f(x) = rel * x + abs`.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct Linear {
|
||||
/// The relative part.
|
||||
pub rel: f64,
|
||||
/// The absolute part.
|
||||
pub abs: f64,
|
||||
}
|
||||
|
||||
impl Linear {
|
||||
/// The constant zero function.
|
||||
pub const ZERO: Linear = Linear { rel: 0.0, abs: 0.0 };
|
||||
|
||||
/// Create a new linear function.
|
||||
pub fn new(rel: f64, abs: f64) -> Self {
|
||||
Self { rel, abs }
|
||||
}
|
||||
|
||||
/// Create a new linear function with only a relative component.
|
||||
pub fn rel(rel: f64) -> Self {
|
||||
Self { rel, abs: 0.0 }
|
||||
}
|
||||
|
||||
/// Create a new linear function with only an absolute component.
|
||||
pub fn abs(abs: f64) -> Self {
|
||||
Self { rel: 0.0, abs }
|
||||
}
|
||||
|
||||
/// Evaluate the linear function with the given value.
|
||||
pub fn eval(self, x: f64) -> f64 {
|
||||
self.rel * x + self.abs
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Linear {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel + other.rel,
|
||||
abs: self.abs + other.abs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Linear {
|
||||
fn add_assign(&mut self, other: Self) {
|
||||
self.rel += other.rel;
|
||||
self.abs += other.abs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Linear {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel - other.rel,
|
||||
abs: self.abs - other.abs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for Linear {
|
||||
fn sub_assign(&mut self, other: Self) {
|
||||
self.rel -= other.rel;
|
||||
self.abs -= other.abs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f64> for Linear {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: f64) -> Self {
|
||||
Self {
|
||||
rel: self.rel * other,
|
||||
abs: self.abs * other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<f64> for Linear {
|
||||
fn mul_assign(&mut self, other: f64) {
|
||||
self.rel *= other;
|
||||
self.abs *= other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Linear> for f64 {
|
||||
type Output = Linear;
|
||||
|
||||
fn mul(self, other: Linear) -> Linear {
|
||||
Linear {
|
||||
rel: self * other.rel,
|
||||
abs: self * other.abs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Linear {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, other: f64) -> Self {
|
||||
Self {
|
||||
rel: self.rel / other,
|
||||
abs: self.abs / other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DivAssign<f64> for Linear {
|
||||
fn div_assign(&mut self, other: f64) {
|
||||
self.rel /= other;
|
||||
self.abs /= other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Linear {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
Self { rel: -self.rel, abs: -self.abs }
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Linear {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}x + {}", self.rel, self.abs)
|
||||
}
|
||||
}
|
48
src/geom/align.rs
Normal file
48
src/geom/align.rs
Normal file
@ -0,0 +1,48 @@
|
||||
use super::*;
|
||||
|
||||
/// Where to align something along a directed axis.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum Align {
|
||||
/// Align at the start of the axis.
|
||||
Start,
|
||||
/// Align in the middle of the axis.
|
||||
Center,
|
||||
/// Align at the end of the axis.
|
||||
End,
|
||||
}
|
||||
|
||||
impl Align {
|
||||
/// Returns the position of this alignment in the given range.
|
||||
pub fn apply(self, range: Range<Length>) -> Length {
|
||||
match self {
|
||||
Self::Start => range.start,
|
||||
Self::Center => (range.start + range.end) / 2.0,
|
||||
Self::End => range.end,
|
||||
}
|
||||
}
|
||||
|
||||
/// The inverse alignment.
|
||||
pub fn inv(self) -> Self {
|
||||
match self {
|
||||
Self::Start => Self::End,
|
||||
Self::Center => Self::Center,
|
||||
Self::End => Self::Start,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Align {
|
||||
fn default() -> Self {
|
||||
Self::Start
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Align {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
Self::Start => "start",
|
||||
Self::Center => "center",
|
||||
Self::End => "end",
|
||||
})
|
||||
}
|
||||
}
|
83
src/geom/dir.rs
Normal file
83
src/geom/dir.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use super::*;
|
||||
|
||||
/// The four directions into which content can be laid out.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Dir {
|
||||
/// Left to right.
|
||||
LTR,
|
||||
/// Right to left.
|
||||
RTL,
|
||||
/// Top to bottom.
|
||||
TTB,
|
||||
/// Bottom to top.
|
||||
BTT,
|
||||
}
|
||||
|
||||
impl Dir {
|
||||
/// The side this direction starts at.
|
||||
pub fn start(self) -> Side {
|
||||
match self {
|
||||
Self::LTR => Side::Left,
|
||||
Self::RTL => Side::Right,
|
||||
Self::TTB => Side::Top,
|
||||
Self::BTT => Side::Bottom,
|
||||
}
|
||||
}
|
||||
|
||||
/// The side this direction ends at.
|
||||
pub fn end(self) -> Side {
|
||||
match self {
|
||||
Self::LTR => Side::Right,
|
||||
Self::RTL => Side::Left,
|
||||
Self::TTB => Side::Bottom,
|
||||
Self::BTT => Side::Top,
|
||||
}
|
||||
}
|
||||
|
||||
/// The specific axis this direction belongs to.
|
||||
pub fn axis(self) -> SpecAxis {
|
||||
match self {
|
||||
Self::LTR | Self::RTL => SpecAxis::Horizontal,
|
||||
Self::TTB | Self::BTT => SpecAxis::Vertical,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this direction points into the positive coordinate direction.
|
||||
///
|
||||
/// The positive directions are left-to-right and top-to-bottom.
|
||||
pub fn is_positive(self) -> bool {
|
||||
match self {
|
||||
Self::LTR | Self::TTB => true,
|
||||
Self::RTL | Self::BTT => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// The factor for this direction.
|
||||
///
|
||||
/// - `1.0` if the direction is positive.
|
||||
/// - `-1.0` if the direction is negative.
|
||||
pub fn factor(self) -> f64 {
|
||||
if self.is_positive() { 1.0 } else { -1.0 }
|
||||
}
|
||||
|
||||
/// The inverse direction.
|
||||
pub fn inv(self) -> Self {
|
||||
match self {
|
||||
Self::LTR => Self::RTL,
|
||||
Self::RTL => Self::LTR,
|
||||
Self::TTB => Self::BTT,
|
||||
Self::BTT => Self::TTB,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Dir {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
Self::LTR => "ltr",
|
||||
Self::RTL => "rtl",
|
||||
Self::TTB => "ttb",
|
||||
Self::BTT => "btt",
|
||||
})
|
||||
}
|
||||
}
|
90
src/geom/gen.rs
Normal file
90
src/geom/gen.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use super::*;
|
||||
|
||||
/// A container with a main and cross component.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct Gen<T> {
|
||||
/// The main component.
|
||||
pub main: T,
|
||||
/// The cross component.
|
||||
pub cross: T,
|
||||
}
|
||||
|
||||
impl<T> Gen<T> {
|
||||
/// Create a new instance from the two components.
|
||||
pub fn new(main: T, cross: T) -> Self {
|
||||
Self { main, cross }
|
||||
}
|
||||
}
|
||||
|
||||
impl Gen<Length> {
|
||||
/// The zero value.
|
||||
pub const ZERO: Self = Self { main: Length::ZERO, cross: Length::ZERO };
|
||||
}
|
||||
|
||||
impl<T> Get<GenAxis> for Gen<T> {
|
||||
type Component = T;
|
||||
|
||||
fn get(self, axis: GenAxis) -> T {
|
||||
match axis {
|
||||
GenAxis::Main => self.main,
|
||||
GenAxis::Cross => self.cross,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, axis: GenAxis) -> &mut T {
|
||||
match axis {
|
||||
GenAxis::Main => &mut self.main,
|
||||
GenAxis::Cross => &mut self.cross,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Switch for Gen<T> {
|
||||
type Other = Spec<T>;
|
||||
|
||||
fn switch(self, dirs: Gen<Dir>) -> Self::Other {
|
||||
match dirs.main.axis() {
|
||||
SpecAxis::Horizontal => Spec::new(self.main, self.cross),
|
||||
SpecAxis::Vertical => Spec::new(self.cross, self.main),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The two generic layouting axes.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum GenAxis {
|
||||
/// The axis pages and paragraphs are set along.
|
||||
Main,
|
||||
/// The axis words and lines are set along.
|
||||
Cross,
|
||||
}
|
||||
|
||||
impl GenAxis {
|
||||
/// The other axis.
|
||||
pub fn other(self) -> Self {
|
||||
match self {
|
||||
Self::Main => Self::Cross,
|
||||
Self::Cross => Self::Main,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Switch for GenAxis {
|
||||
type Other = SpecAxis;
|
||||
|
||||
fn switch(self, dirs: Gen<Dir>) -> Self::Other {
|
||||
match self {
|
||||
Self::Main => dirs.main.axis(),
|
||||
Self::Cross => dirs.cross.axis(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for GenAxis {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
Self::Main => "main",
|
||||
Self::Cross => "cross",
|
||||
})
|
||||
}
|
||||
}
|
213
src/geom/length.rs
Normal file
213
src/geom/length.rs
Normal file
@ -0,0 +1,213 @@
|
||||
use super::*;
|
||||
|
||||
/// An absolute length.
|
||||
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
|
||||
pub struct Length {
|
||||
/// The length in raw units.
|
||||
raw: f64,
|
||||
}
|
||||
|
||||
impl Length {
|
||||
/// The zero length.
|
||||
pub const ZERO: Self = Self { raw: 0.0 };
|
||||
|
||||
/// Create a length from a number of points.
|
||||
pub fn pt(pt: f64) -> Self {
|
||||
Self::with_unit(pt, Unit::Pt)
|
||||
}
|
||||
|
||||
/// Create a length from a number of millimeters.
|
||||
pub fn mm(mm: f64) -> Self {
|
||||
Self::with_unit(mm, Unit::Mm)
|
||||
}
|
||||
|
||||
/// Create a length from a number of centimeters.
|
||||
pub fn cm(cm: f64) -> Self {
|
||||
Self::with_unit(cm, Unit::Cm)
|
||||
}
|
||||
|
||||
/// Create a length from a number of inches.
|
||||
pub fn inches(inches: f64) -> Self {
|
||||
Self::with_unit(inches, Unit::In)
|
||||
}
|
||||
|
||||
/// Create a length from a number of raw units.
|
||||
pub fn raw(raw: f64) -> Self {
|
||||
Self { raw }
|
||||
}
|
||||
|
||||
/// Convert this to a number of points.
|
||||
pub fn to_pt(self) -> f64 {
|
||||
self.to_unit(Unit::Pt)
|
||||
}
|
||||
|
||||
/// Convert this to a number of millimeters.
|
||||
pub fn to_mm(self) -> f64 {
|
||||
self.to_unit(Unit::Mm)
|
||||
}
|
||||
|
||||
/// Convert this to a number of centimeters.
|
||||
pub fn to_cm(self) -> f64 {
|
||||
self.to_unit(Unit::Cm)
|
||||
}
|
||||
|
||||
/// Convert this to a number of inches.
|
||||
pub fn to_inches(self) -> f64 {
|
||||
self.to_unit(Unit::In)
|
||||
}
|
||||
|
||||
/// Get the value of this length in raw units.
|
||||
pub fn to_raw(self) -> f64 {
|
||||
self.raw
|
||||
}
|
||||
|
||||
/// Create a length from a value in a unit.
|
||||
pub fn with_unit(val: f64, unit: Unit) -> Self {
|
||||
Self { raw: val * unit.raw_scale() }
|
||||
}
|
||||
|
||||
/// Get the value of this length in unit.
|
||||
pub fn to_unit(self, unit: Unit) -> f64 {
|
||||
self.raw / unit.raw_scale()
|
||||
}
|
||||
|
||||
/// The minimum of this and another length.
|
||||
pub fn min(self, other: Self) -> Self {
|
||||
Self { raw: self.raw.min(other.raw) }
|
||||
}
|
||||
|
||||
/// The maximum of this and another length.
|
||||
pub fn max(self, other: Self) -> Self {
|
||||
Self { raw: self.raw.max(other.raw) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Length {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
// Format small lengths as points and large ones as centimeters.
|
||||
let (val, unit) = if self.to_pt().abs() < 25.0 {
|
||||
(self.to_pt(), Unit::Pt)
|
||||
} else {
|
||||
(self.to_cm(), Unit::Cm)
|
||||
};
|
||||
write!(f, "{:.2}{}", val, unit)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Length {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Length {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
Self { raw: -self.raw }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Length {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self { raw: self.raw + other.raw }
|
||||
}
|
||||
}
|
||||
|
||||
sub_impl!(Length - Length -> Length);
|
||||
|
||||
impl Mul<f64> for Length {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: f64) -> Self {
|
||||
Self { raw: self.raw * other }
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Length> for f64 {
|
||||
type Output = Length;
|
||||
|
||||
fn mul(self, other: Length) -> Length {
|
||||
other * self
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Length {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, other: f64) -> Self {
|
||||
Self { raw: self.raw / other }
|
||||
}
|
||||
}
|
||||
|
||||
assign_impl!(Length += Length);
|
||||
assign_impl!(Length -= Length);
|
||||
assign_impl!(Length *= f64);
|
||||
assign_impl!(Length /= f64);
|
||||
|
||||
impl Sum for Length {
|
||||
fn sum<I: Iterator<Item = Length>>(iter: I) -> Self {
|
||||
iter.fold(Length::ZERO, Add::add)
|
||||
}
|
||||
}
|
||||
|
||||
/// Different units of measurement.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum Unit {
|
||||
/// Points.
|
||||
Pt,
|
||||
/// Millimeters.
|
||||
Mm,
|
||||
/// Centimeters.
|
||||
Cm,
|
||||
/// Inches.
|
||||
In,
|
||||
}
|
||||
|
||||
impl Unit {
|
||||
/// How many raw units correspond to a value of `1.0` in this unit.
|
||||
fn raw_scale(self) -> f64 {
|
||||
match self {
|
||||
Unit::Pt => 1.0,
|
||||
Unit::Mm => 2.83465,
|
||||
Unit::Cm => 28.3465,
|
||||
Unit::In => 72.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Unit {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
Unit::Mm => "mm",
|
||||
Unit::Pt => "pt",
|
||||
Unit::Cm => "cm",
|
||||
Unit::In => "in",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Unit {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_length_formats_correctly() {
|
||||
assert_eq!(Length::pt(-28.34).to_string(), "-1.00cm".to_string());
|
||||
assert_eq!(Length::pt(23.0).to_string(), "23.00pt".to_string());
|
||||
assert_eq!(Length::cm(12.728).to_string(), "12.73cm".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_length_unit_conversion() {
|
||||
assert!((Length::mm(150.0).to_cm() - 15.0) < 1e-4);
|
||||
}
|
||||
}
|
172
src/geom/linear.rs
Normal file
172
src/geom/linear.rs
Normal file
@ -0,0 +1,172 @@
|
||||
use super::*;
|
||||
|
||||
/// A combined relative and absolute length.
|
||||
#[derive(Default, Copy, Clone, PartialEq)]
|
||||
pub struct Linear {
|
||||
/// The relative part.
|
||||
pub rel: Relative,
|
||||
/// The absolute part.
|
||||
pub abs: Length,
|
||||
}
|
||||
|
||||
impl Linear {
|
||||
/// The zero linear.
|
||||
pub const ZERO: Linear = Linear { rel: Relative::ZERO, abs: Length::ZERO };
|
||||
|
||||
/// Create a new linear.
|
||||
pub fn new(rel: Relative, abs: Length) -> Self {
|
||||
Self { rel, abs }
|
||||
}
|
||||
|
||||
/// Evaluate the linear length with `one` being `100%` for the relative
|
||||
/// part.
|
||||
pub fn eval(self, one: Length) -> Length {
|
||||
self.rel.eval(one) + self.abs
|
||||
}
|
||||
|
||||
/// Whether this linear's relative component is zero.
|
||||
pub fn is_absolute(self) -> bool {
|
||||
self.rel == Relative::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Linear {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{} + {}", self.rel, self.abs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Linear {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Length> for Linear {
|
||||
fn from(abs: Length) -> Self {
|
||||
Self { rel: Relative::ZERO, abs }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Relative> for Linear {
|
||||
fn from(rel: Relative) -> Self {
|
||||
Self { rel, abs: Length::ZERO }
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Linear {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
Self { rel: -self.rel, abs: -self.abs }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Linear {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel + other.rel,
|
||||
abs: self.abs + other.abs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Relative> for Length {
|
||||
type Output = Linear;
|
||||
|
||||
fn add(self, other: Relative) -> Linear {
|
||||
Linear { rel: other, abs: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Length> for Relative {
|
||||
type Output = Linear;
|
||||
|
||||
fn add(self, other: Length) -> Linear {
|
||||
other + self
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Length> for Linear {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Length) -> Self {
|
||||
Self { rel: self.rel, abs: self.abs + other }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Linear> for Length {
|
||||
type Output = Linear;
|
||||
|
||||
fn add(self, other: Linear) -> Linear {
|
||||
other + self
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Relative> for Linear {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Relative) -> Self {
|
||||
Self { rel: self.rel + other, abs: self.abs }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Linear> for Relative {
|
||||
type Output = Linear;
|
||||
|
||||
fn add(self, other: Linear) -> Linear {
|
||||
other + self
|
||||
}
|
||||
}
|
||||
|
||||
sub_impl!(Linear - Linear -> Linear);
|
||||
sub_impl!(Length - Relative -> Linear);
|
||||
sub_impl!(Relative - Length -> Linear);
|
||||
sub_impl!(Linear - Length -> Linear);
|
||||
sub_impl!(Length - Linear -> Linear);
|
||||
sub_impl!(Linear - Relative -> Linear);
|
||||
sub_impl!(Relative - Linear -> Linear);
|
||||
|
||||
impl Mul<f64> for Linear {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: f64) -> Self {
|
||||
Self {
|
||||
rel: self.rel * other,
|
||||
abs: self.abs * other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Linear> for f64 {
|
||||
type Output = Linear;
|
||||
|
||||
fn mul(self, other: Linear) -> Linear {
|
||||
Linear {
|
||||
rel: self * other.rel,
|
||||
abs: self * other.abs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Linear {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, other: f64) -> Self {
|
||||
Self {
|
||||
rel: self.rel / other,
|
||||
abs: self.abs / other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assign_impl!(Linear += Linear);
|
||||
assign_impl!(Linear += Length);
|
||||
assign_impl!(Linear += Relative);
|
||||
assign_impl!(Linear -= Linear);
|
||||
assign_impl!(Linear -= Length);
|
||||
assign_impl!(Linear -= Relative);
|
||||
assign_impl!(Linear *= f64);
|
||||
assign_impl!(Linear /= f64);
|
47
src/geom/macros.rs
Normal file
47
src/geom/macros.rs
Normal file
@ -0,0 +1,47 @@
|
||||
/// Implement the `Sub` trait based on existing `Neg` and `Add` impls.
|
||||
macro_rules! sub_impl {
|
||||
($a:ident - $b:ident -> $c:ident) => {
|
||||
impl Sub<$b> for $a {
|
||||
type Output = $c;
|
||||
|
||||
fn sub(self, other: $b) -> $c {
|
||||
self + -other
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Implement an assign trait based on an existing non-assign trait.
|
||||
macro_rules! assign_impl {
|
||||
($a:ident += $b:ident) => {
|
||||
impl AddAssign<$b> for $a {
|
||||
fn add_assign(&mut self, other: $b) {
|
||||
*self = *self + other;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
($a:ident -= $b:ident) => {
|
||||
impl SubAssign<$b> for $a {
|
||||
fn sub_assign(&mut self, other: $b) {
|
||||
*self = *self - other;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
($a:ident *= $b:ident) => {
|
||||
impl MulAssign<$b> for $a {
|
||||
fn mul_assign(&mut self, other: $b) {
|
||||
*self = *self * other;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
($a:ident /= $b:ident) => {
|
||||
impl DivAssign<$b> for $a {
|
||||
fn div_assign(&mut self, other: $b) {
|
||||
*self = *self / other;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
53
src/geom/mod.rs
Normal file
53
src/geom/mod.rs
Normal file
@ -0,0 +1,53 @@
|
||||
//! Geometrical primitivies.
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
mod align;
|
||||
mod dir;
|
||||
mod gen;
|
||||
mod length;
|
||||
mod linear;
|
||||
mod point;
|
||||
mod relative;
|
||||
mod sides;
|
||||
mod size;
|
||||
mod spec;
|
||||
|
||||
pub use align::*;
|
||||
pub use dir::*;
|
||||
pub use gen::*;
|
||||
pub use length::*;
|
||||
pub use linear::*;
|
||||
pub use point::*;
|
||||
pub use relative::*;
|
||||
pub use sides::*;
|
||||
pub use size::*;
|
||||
pub use spec::*;
|
||||
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::iter::Sum;
|
||||
use std::ops::*;
|
||||
|
||||
/// Generic access to a structure's components.
|
||||
pub trait Get<Index> {
|
||||
/// The structure's component type.
|
||||
type Component;
|
||||
|
||||
/// Return the component for the specified index.
|
||||
fn get(self, index: Index) -> Self::Component;
|
||||
|
||||
/// Borrow the component for the specified index mutably.
|
||||
fn get_mut(&mut self, index: Index) -> &mut Self::Component;
|
||||
}
|
||||
|
||||
/// Switch between the specific and generic representations of a type.
|
||||
///
|
||||
/// The generic representation deals with main and cross axes while the specific
|
||||
/// representation deals with horizontal and vertical axes.
|
||||
pub trait Switch {
|
||||
/// The type of the other version.
|
||||
type Other;
|
||||
|
||||
/// The other version of this type based on the current directions.
|
||||
fn switch(self, dirs: Gen<Dir>) -> Self::Other;
|
||||
}
|
102
src/geom/point.rs
Normal file
102
src/geom/point.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use super::*;
|
||||
|
||||
/// A point in 2D.
|
||||
#[derive(Default, Copy, Clone, PartialEq)]
|
||||
pub struct Point {
|
||||
/// The x coordinate.
|
||||
pub x: Length,
|
||||
/// The y coordinate.
|
||||
pub y: Length,
|
||||
}
|
||||
|
||||
impl Point {
|
||||
/// The origin point.
|
||||
pub const ZERO: Self = Self { x: Length::ZERO, y: Length::ZERO };
|
||||
|
||||
/// Create a new point from x and y coordinate.
|
||||
pub fn new(x: Length, y: Length) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
impl Get<SpecAxis> for Point {
|
||||
type Component = Length;
|
||||
|
||||
fn get(self, axis: SpecAxis) -> Length {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => self.x,
|
||||
SpecAxis::Vertical => self.y,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, axis: SpecAxis) -> &mut Length {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => &mut self.x,
|
||||
SpecAxis::Vertical => &mut self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Switch for Point {
|
||||
type Other = Gen<Length>;
|
||||
|
||||
fn switch(self, dirs: Gen<Dir>) -> Self::Other {
|
||||
match dirs.main.axis() {
|
||||
SpecAxis::Horizontal => Gen::new(self.x, self.y),
|
||||
SpecAxis::Vertical => Gen::new(self.y, self.x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Point {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "({}, {})", self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Point {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
Self { x: -self.x, y: -self.y }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Point {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self { x: self.x + other.x, y: self.y + other.y }
|
||||
}
|
||||
}
|
||||
|
||||
sub_impl!(Point - Point -> Point);
|
||||
|
||||
impl Mul<f64> for Point {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: f64) -> Self {
|
||||
Self { x: self.x * other, y: self.y * other }
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Point> for f64 {
|
||||
type Output = Point;
|
||||
|
||||
fn mul(self, other: Point) -> Point {
|
||||
other * self
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Point {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, other: f64) -> Self {
|
||||
Self { x: self.x / other, y: self.y / other }
|
||||
}
|
||||
}
|
||||
|
||||
assign_impl!(Point += Point);
|
||||
assign_impl!(Point -= Point);
|
||||
assign_impl!(Point *= f64);
|
||||
assign_impl!(Point /= f64);
|
92
src/geom/relative.rs
Normal file
92
src/geom/relative.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use super::*;
|
||||
|
||||
/// A relative length.
|
||||
///
|
||||
/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the
|
||||
/// corresponding [literal].
|
||||
///
|
||||
/// [literal]: ../syntax/ast/enum.Lit.html#variant.Percent
|
||||
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
|
||||
pub struct Relative(f64);
|
||||
|
||||
impl Relative {
|
||||
/// A ratio of `0%` represented as `0.0`.
|
||||
pub const ZERO: Self = Self(0.0);
|
||||
|
||||
/// A ratio of `100%` represented as `1.0`.
|
||||
pub const ONE: Self = Self(1.0);
|
||||
|
||||
/// Create a new relative value.
|
||||
pub fn new(ratio: f64) -> Self {
|
||||
Self(ratio)
|
||||
}
|
||||
|
||||
/// Get the underlying ratio.
|
||||
pub fn get(self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Evaluate the relative length with `one` being `100%`.
|
||||
pub fn eval(self, one: Length) -> Length {
|
||||
self.get() * one
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Relative {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{:.2}%", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Relative {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Relative {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
Self(-self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Relative {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
sub_impl!(Relative - Relative -> Relative);
|
||||
|
||||
impl Mul<f64> for Relative {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: f64) -> Self {
|
||||
Self(self.0 * other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Relative> for f64 {
|
||||
type Output = Relative;
|
||||
|
||||
fn mul(self, other: Relative) -> Relative {
|
||||
other * self
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Relative {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, other: f64) -> Self {
|
||||
Self(self.0 / other)
|
||||
}
|
||||
}
|
||||
|
||||
assign_impl!(Relative += Relative);
|
||||
assign_impl!(Relative -= Relative);
|
||||
assign_impl!(Relative *= f64);
|
||||
assign_impl!(Relative /= f64);
|
101
src/geom/sides.rs
Normal file
101
src/geom/sides.rs
Normal file
@ -0,0 +1,101 @@
|
||||
use super::*;
|
||||
|
||||
/// A container with left, top, right and bottom components.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct Sides<T> {
|
||||
/// The value for the left side.
|
||||
pub left: T,
|
||||
/// The value for the top side.
|
||||
pub top: T,
|
||||
/// The value for the right side.
|
||||
pub right: T,
|
||||
/// The value for the bottom side.
|
||||
pub bottom: T,
|
||||
}
|
||||
|
||||
impl<T> Sides<T> {
|
||||
/// Create a new box from four sizes.
|
||||
pub fn new(left: T, top: T, right: T, bottom: T) -> Self {
|
||||
Self { left, top, right, bottom }
|
||||
}
|
||||
|
||||
/// Create an instance with all four components set to the same `value`.
|
||||
pub fn uniform(value: T) -> Self
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
Self {
|
||||
left: value.clone(),
|
||||
top: value.clone(),
|
||||
right: value.clone(),
|
||||
bottom: value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sides<Linear> {
|
||||
/// Evaluate the linear values in this container.
|
||||
pub fn eval(self, size: Size) -> Sides<Length> {
|
||||
Sides {
|
||||
left: self.left.eval(size.width),
|
||||
top: self.top.eval(size.height),
|
||||
right: self.right.eval(size.width),
|
||||
bottom: self.bottom.eval(size.height),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sides<Length> {
|
||||
/// A size with `left` and `right` summed into `width`, and `top` and
|
||||
/// `bottom` summed into `height`.
|
||||
pub fn size(self) -> Size {
|
||||
Size::new(self.left + self.right, self.top + self.bottom)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Get<Side> for Sides<T> {
|
||||
type Component = T;
|
||||
|
||||
fn get(self, side: Side) -> T {
|
||||
match side {
|
||||
Side::Left => self.left,
|
||||
Side::Top => self.top,
|
||||
Side::Right => self.right,
|
||||
Side::Bottom => self.bottom,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, side: Side) -> &mut T {
|
||||
match side {
|
||||
Side::Left => &mut self.left,
|
||||
Side::Top => &mut self.top,
|
||||
Side::Right => &mut self.right,
|
||||
Side::Bottom => &mut self.bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The four sides of objects.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Side {
|
||||
/// The left side.
|
||||
Left,
|
||||
/// The top side.
|
||||
Top,
|
||||
/// The right side.
|
||||
Right,
|
||||
/// The bottom side.
|
||||
Bottom,
|
||||
}
|
||||
|
||||
impl Side {
|
||||
/// The opposite side.
|
||||
pub fn inv(self) -> Self {
|
||||
match self {
|
||||
Self::Left => Self::Right,
|
||||
Self::Top => Self::Bottom,
|
||||
Self::Right => Self::Left,
|
||||
Self::Bottom => Self::Top,
|
||||
}
|
||||
}
|
||||
}
|
119
src/geom/size.rs
Normal file
119
src/geom/size.rs
Normal file
@ -0,0 +1,119 @@
|
||||
use super::*;
|
||||
|
||||
/// A size in 2D.
|
||||
#[derive(Default, Copy, Clone, PartialEq)]
|
||||
pub struct Size {
|
||||
/// The width.
|
||||
pub width: Length,
|
||||
/// The height.
|
||||
pub height: Length,
|
||||
}
|
||||
|
||||
impl Size {
|
||||
/// The zero size.
|
||||
pub const ZERO: Self = Self {
|
||||
width: Length::ZERO,
|
||||
height: Length::ZERO,
|
||||
};
|
||||
|
||||
/// Create a new size from width and height.
|
||||
pub fn new(width: Length, height: Length) -> Self {
|
||||
Self { width, height }
|
||||
}
|
||||
|
||||
/// Whether the other size fits into this one (smaller width and height).
|
||||
pub fn fits(self, other: Self) -> bool {
|
||||
self.width >= other.width && self.height >= other.height
|
||||
}
|
||||
}
|
||||
|
||||
impl Get<SpecAxis> for Size {
|
||||
type Component = Length;
|
||||
|
||||
fn get(self, axis: SpecAxis) -> Length {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => self.width,
|
||||
SpecAxis::Vertical => self.height,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, axis: SpecAxis) -> &mut Length {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => &mut self.width,
|
||||
SpecAxis::Vertical => &mut self.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Switch for Size {
|
||||
type Other = Gen<Length>;
|
||||
|
||||
fn switch(self, dirs: Gen<Dir>) -> Self::Other {
|
||||
match dirs.main.axis() {
|
||||
SpecAxis::Horizontal => Gen::new(self.width, self.height),
|
||||
SpecAxis::Vertical => Gen::new(self.height, self.width),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Size {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "({} x {})", self.width, self.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
Self { width: -self.width, height: -self.height }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self {
|
||||
width: self.width + other.width,
|
||||
height: self.height + other.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub_impl!(Size - Size -> Size);
|
||||
|
||||
impl Mul<f64> for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: f64) -> Self {
|
||||
Self {
|
||||
width: self.width * other,
|
||||
height: self.height * other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Size> for f64 {
|
||||
type Output = Size;
|
||||
|
||||
fn mul(self, other: Size) -> Size {
|
||||
other * self
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, other: f64) -> Self {
|
||||
Self {
|
||||
width: self.width / other,
|
||||
height: self.height / other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assign_impl!(Size -= Size);
|
||||
assign_impl!(Size += Size);
|
||||
assign_impl!(Size *= f64);
|
||||
assign_impl!(Size /= f64);
|
105
src/geom/spec.rs
Normal file
105
src/geom/spec.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use super::*;
|
||||
|
||||
/// A container with a horizontal and vertical component.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct Spec<T> {
|
||||
/// The horizontal component.
|
||||
pub horizontal: T,
|
||||
/// The vertical component.
|
||||
pub vertical: T,
|
||||
}
|
||||
|
||||
impl<T> Spec<T> {
|
||||
/// Create a new instance from the two components.
|
||||
pub fn new(horizontal: T, vertical: T) -> Self {
|
||||
Self { horizontal, vertical }
|
||||
}
|
||||
}
|
||||
|
||||
impl Spec<Length> {
|
||||
/// The zero value.
|
||||
pub const ZERO: Self = Self {
|
||||
horizontal: Length::ZERO,
|
||||
vertical: Length::ZERO,
|
||||
};
|
||||
|
||||
/// Convert to a point.
|
||||
pub fn to_point(self) -> Point {
|
||||
Point::new(self.horizontal, self.vertical)
|
||||
}
|
||||
|
||||
/// Convert to a size.
|
||||
pub fn to_size(self) -> Size {
|
||||
Size::new(self.horizontal, self.vertical)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Get<SpecAxis> for Spec<T> {
|
||||
type Component = T;
|
||||
|
||||
fn get(self, axis: SpecAxis) -> T {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => self.horizontal,
|
||||
SpecAxis::Vertical => self.vertical,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, axis: SpecAxis) -> &mut T {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => &mut self.horizontal,
|
||||
SpecAxis::Vertical => &mut self.vertical,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Switch for Spec<T> {
|
||||
type Other = Gen<T>;
|
||||
|
||||
fn switch(self, dirs: Gen<Dir>) -> Self::Other {
|
||||
match dirs.main.axis() {
|
||||
SpecAxis::Horizontal => Gen::new(self.horizontal, self.vertical),
|
||||
SpecAxis::Vertical => Gen::new(self.vertical, self.horizontal),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The two specific layouting axes.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum SpecAxis {
|
||||
/// The vertical layouting axis.
|
||||
Vertical,
|
||||
/// The horizontal layouting axis.
|
||||
Horizontal,
|
||||
}
|
||||
|
||||
impl SpecAxis {
|
||||
/// The other axis.
|
||||
pub fn other(self) -> Self {
|
||||
match self {
|
||||
Self::Horizontal => Self::Vertical,
|
||||
Self::Vertical => Self::Horizontal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Switch for SpecAxis {
|
||||
type Other = GenAxis;
|
||||
|
||||
fn switch(self, dirs: Gen<Dir>) -> Self::Other {
|
||||
if self == dirs.main.axis() {
|
||||
GenAxis::Main
|
||||
} else {
|
||||
debug_assert_eq!(self, dirs.cross.axis());
|
||||
GenAxis::Cross
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SpecAxis {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
Self::Vertical => "vertical",
|
||||
Self::Horizontal => "horizontal",
|
||||
})
|
||||
}
|
||||
}
|
@ -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>,
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
@ -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)
|
@ -7,11 +7,11 @@ use super::*;
|
||||
/// the main axis by the height of the previous line plus extra line spacing.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Par {
|
||||
pub dirs: Gen2<Dir>,
|
||||
pub line_spacing: f64,
|
||||
pub dirs: Gen<Dir>,
|
||||
pub line_spacing: Length,
|
||||
pub children: Vec<LayoutNode>,
|
||||
pub aligns: Gen2<GenAlign>,
|
||||
pub expand: Spec2<bool>,
|
||||
pub aligns: Gen<Align>,
|
||||
pub expand: Spec<bool>,
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
@ -73,17 +73,17 @@ struct LineLayouter {
|
||||
#[derive(Debug, Clone)]
|
||||
struct LineContext {
|
||||
/// The layout directions.
|
||||
dirs: Gen2<Dir>,
|
||||
dirs: Gen<Dir>,
|
||||
/// The spaces to layout into.
|
||||
spaces: Vec<LayoutSpace>,
|
||||
/// Whether to spill over into copies of the last space or finish layouting
|
||||
/// when the last space is used up.
|
||||
repeat: bool,
|
||||
/// The spacing to be inserted between each pair of lines.
|
||||
line_spacing: f64,
|
||||
line_spacing: Length,
|
||||
/// Whether to expand the size of the resulting layout to the full size of
|
||||
/// this space or to shrink it to fit the content.
|
||||
expand: Spec2<bool>,
|
||||
expand: Spec<bool>,
|
||||
}
|
||||
|
||||
impl LineLayouter {
|
||||
@ -102,7 +102,7 @@ impl LineLayouter {
|
||||
}
|
||||
|
||||
/// Add a layout.
|
||||
fn push_box(&mut self, layout: BoxLayout, aligns: Gen2<GenAlign>) {
|
||||
fn push_box(&mut self, layout: BoxLayout, aligns: Gen<Align>) {
|
||||
let dirs = self.ctx.dirs;
|
||||
if let Some(prev) = self.run.aligns {
|
||||
if aligns.main != prev.main {
|
||||
@ -124,9 +124,9 @@ impl LineLayouter {
|
||||
|
||||
// FIXME: Alignment in non-expanding parent.
|
||||
rest_run.usable = Some(match aligns.cross {
|
||||
GenAlign::Start => unreachable!("start > x"),
|
||||
GenAlign::Center => usable - 2.0 * self.run.size.cross,
|
||||
GenAlign::End => usable - self.run.size.cross,
|
||||
Align::Start => unreachable!("start > x"),
|
||||
Align::Center => usable - 2.0 * self.run.size.cross,
|
||||
Align::End => usable - self.run.size.cross,
|
||||
});
|
||||
|
||||
self.finish_line();
|
||||
@ -160,7 +160,7 @@ impl LineLayouter {
|
||||
}
|
||||
|
||||
/// Add spacing to the line.
|
||||
fn push_spacing(&mut self, mut spacing: f64) {
|
||||
fn push_spacing(&mut self, mut spacing: Length) {
|
||||
spacing = spacing.min(self.usable().cross);
|
||||
self.run.size.cross += spacing;
|
||||
}
|
||||
@ -169,7 +169,7 @@ impl LineLayouter {
|
||||
///
|
||||
/// This specifies how much more would fit before a line break would be
|
||||
/// needed.
|
||||
fn usable(&self) -> Gen2<f64> {
|
||||
fn usable(&self) -> Gen<Length> {
|
||||
// The base is the usable space of the stack layouter.
|
||||
let mut usable = self.stack.usable().switch(self.ctx.dirs);
|
||||
|
||||
@ -192,7 +192,7 @@ impl LineLayouter {
|
||||
|
||||
/// Whether the currently set line is empty.
|
||||
fn line_is_empty(&self) -> bool {
|
||||
self.run.size == Gen2::ZERO && self.run.layouts.is_empty()
|
||||
self.run.size == Gen::ZERO && self.run.layouts.is_empty()
|
||||
}
|
||||
|
||||
/// Finish everything up and return the final collection of boxes.
|
||||
@ -224,7 +224,7 @@ impl LineLayouter {
|
||||
self.run.size.cross - offset - child.size.get(dirs.cross.axis())
|
||||
};
|
||||
|
||||
let pos = Gen2::new(0.0, cross).switch(dirs).to_point();
|
||||
let pos = Gen::new(Length::ZERO, cross).switch(dirs).to_point();
|
||||
layout.push_layout(pos, child);
|
||||
}
|
||||
|
||||
@ -244,25 +244,25 @@ impl LineLayouter {
|
||||
/// multiple runs with different alignments.
|
||||
struct LineRun {
|
||||
/// The so-far accumulated items of the run.
|
||||
layouts: Vec<(f64, BoxLayout)>,
|
||||
layouts: Vec<(Length, BoxLayout)>,
|
||||
/// The summed width and maximal height of the run.
|
||||
size: Gen2<f64>,
|
||||
size: Gen<Length>,
|
||||
/// The alignment of all layouts in the line.
|
||||
///
|
||||
/// When a new run is created the alignment is yet to be determined and
|
||||
/// `None` as such. Once a layout is added, its alignment decides the
|
||||
/// alignment for the whole run.
|
||||
aligns: Option<Gen2<GenAlign>>,
|
||||
aligns: Option<Gen<Align>>,
|
||||
/// The amount of cross-space left by another run on the same line or `None`
|
||||
/// if this is the only run so far.
|
||||
usable: Option<f64>,
|
||||
usable: Option<Length>,
|
||||
}
|
||||
|
||||
impl LineRun {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
layouts: vec![],
|
||||
size: Gen2::ZERO,
|
||||
size: Gen::ZERO,
|
||||
aligns: None,
|
||||
usable: None,
|
||||
}
|
||||
@ -283,7 +283,7 @@ pub(super) struct StackLayouter {
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct StackContext {
|
||||
/// The layouting directions.
|
||||
pub dirs: Gen2<Dir>,
|
||||
pub dirs: Gen<Dir>,
|
||||
/// The spaces to layout into.
|
||||
pub spaces: Vec<LayoutSpace>,
|
||||
/// Whether to spill over into copies of the last space or finish layouting
|
||||
@ -291,7 +291,7 @@ pub(super) struct StackContext {
|
||||
pub repeat: bool,
|
||||
/// Whether to expand the size of the resulting layout to the full size of
|
||||
/// this space or to shrink it to fit the content.
|
||||
pub expand: Spec2<bool>,
|
||||
pub expand: Spec<bool>,
|
||||
}
|
||||
|
||||
impl StackLayouter {
|
||||
@ -306,7 +306,7 @@ impl StackLayouter {
|
||||
}
|
||||
|
||||
/// Add a layout to the stack.
|
||||
pub fn push_box(&mut self, layout: BoxLayout, aligns: Gen2<GenAlign>) {
|
||||
pub fn push_box(&mut self, layout: BoxLayout, aligns: Gen<Align>) {
|
||||
// If the alignment cannot be fitted in this space, finish it.
|
||||
//
|
||||
// TODO: Issue warning for non-fitting alignment in non-repeating
|
||||
@ -331,20 +331,20 @@ impl StackLayouter {
|
||||
}
|
||||
|
||||
/// Add spacing to the stack.
|
||||
pub fn push_spacing(&mut self, mut spacing: f64) {
|
||||
pub fn push_spacing(&mut self, mut spacing: Length) {
|
||||
// Reduce the spacing such that it definitely fits.
|
||||
let axis = self.ctx.dirs.main.axis();
|
||||
spacing = spacing.min(self.space.usable.get(axis));
|
||||
|
||||
let size = Gen2::new(spacing, 0.0);
|
||||
let size = Gen::new(spacing, Length::ZERO);
|
||||
self.update_metrics(size);
|
||||
self.space.layouts.push((
|
||||
BoxLayout::new(size.switch(self.ctx.dirs).to_size()),
|
||||
Gen2::default(),
|
||||
Gen::default(),
|
||||
));
|
||||
}
|
||||
|
||||
fn update_metrics(&mut self, added: Gen2<f64>) {
|
||||
fn update_metrics(&mut self, added: Gen<Length>) {
|
||||
let mut used = self.space.used.switch(self.ctx.dirs);
|
||||
used.cross = used.cross.max(added.cross);
|
||||
used.main += added.main;
|
||||
@ -398,11 +398,7 @@ impl StackLayouter {
|
||||
/// Finish active current space and start a new one.
|
||||
pub fn finish_space(&mut self, hard: bool) {
|
||||
let dirs = self.ctx.dirs;
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Step 1: Determine the full size of the space.
|
||||
// (Mostly done already while collecting the boxes, but here we
|
||||
// expand if necessary.)
|
||||
let main = dirs.main.axis();
|
||||
|
||||
let space = self.ctx.spaces[self.space.index];
|
||||
let layout_size = {
|
||||
@ -416,64 +412,44 @@ impl StackLayouter {
|
||||
used_size
|
||||
};
|
||||
|
||||
let mut sum = Length::ZERO;
|
||||
let mut sums = Vec::with_capacity(self.space.layouts.len() + 1);
|
||||
|
||||
for (boxed, _) in &self.space.layouts {
|
||||
sums.push(sum);
|
||||
sum += boxed.size.get(main);
|
||||
}
|
||||
|
||||
sums.push(sum);
|
||||
|
||||
let mut layout = BoxLayout::new(layout_size);
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Step 2: Forward pass. Create a bounding box for each layout in which
|
||||
// it will be aligned. Then, go forwards through the boxes and remove
|
||||
// what is taken by previous layouts from the following layouts.
|
||||
|
||||
let mut bounds = vec![];
|
||||
let mut bound = Rect {
|
||||
x0: 0.0,
|
||||
y0: 0.0,
|
||||
x1: layout_size.width,
|
||||
y1: layout_size.height,
|
||||
};
|
||||
|
||||
for (layout, _) in &self.space.layouts {
|
||||
// First, store the bounds calculated so far (which were reduced
|
||||
// by the predecessors of this layout) as the initial bounding box
|
||||
// of this layout.
|
||||
bounds.push(bound);
|
||||
|
||||
// Then, reduce the bounding box for the following layouts. This
|
||||
// layout uses up space from the origin to the end. Thus, it reduces
|
||||
// the usable space for following layouts at its origin by its
|
||||
// main-axis extent.
|
||||
*bound.get_mut(dirs.main.start()) +=
|
||||
dirs.main.factor() * layout.size.get(dirs.main.axis());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Step 3: Backward pass. Reduce the bounding boxes from the previous
|
||||
// layouts by what is taken by the following ones.
|
||||
|
||||
let mut main_extent = 0.0;
|
||||
for (child, bound) in self.space.layouts.iter().zip(&mut bounds).rev() {
|
||||
let (layout, _) = child;
|
||||
|
||||
// Reduce the bounding box of this layout by the following one's
|
||||
// main-axis extents.
|
||||
*bound.get_mut(dirs.main.end()) -= dirs.main.factor() * main_extent;
|
||||
|
||||
// And then, include this layout's main-axis extent.
|
||||
main_extent += layout.size.get(dirs.main.axis());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Step 4: Align each layout in its bounding box and collect everything
|
||||
// into a single finished layout.
|
||||
let used = layout_size.switch(dirs);
|
||||
|
||||
let children = std::mem::take(&mut self.space.layouts);
|
||||
for ((child, aligns), bound) in children.into_iter().zip(bounds) {
|
||||
// Align the child in its own bounds.
|
||||
let local =
|
||||
bound.size().anchor(dirs, aligns) - child.size.anchor(dirs, aligns);
|
||||
for (i, (boxed, aligns)) in children.into_iter().enumerate() {
|
||||
let size = boxed.size.switch(dirs);
|
||||
|
||||
// Make the local position in the bounds global.
|
||||
let pos = bound.origin() + local;
|
||||
layout.push_layout(pos, child);
|
||||
let before = sums[i];
|
||||
let after = sum - sums[i + 1];
|
||||
let main_len = used.main - size.main;
|
||||
let main_range = if dirs.main.is_positive() {
|
||||
before .. main_len - after
|
||||
} else {
|
||||
main_len - before .. after
|
||||
};
|
||||
|
||||
let cross_len = used.cross - size.cross;
|
||||
let cross_range = if dirs.cross.is_positive() {
|
||||
Length::ZERO .. cross_len
|
||||
} else {
|
||||
cross_len .. Length::ZERO
|
||||
};
|
||||
|
||||
let main = aligns.main.apply(main_range);
|
||||
let cross = aligns.cross.apply(cross_range);
|
||||
let pos = Gen::new(main, cross).switch(dirs).to_point();
|
||||
|
||||
layout.push_layout(pos, boxed);
|
||||
}
|
||||
|
||||
self.layouts.push(layout);
|
||||
@ -503,7 +479,7 @@ pub(super) struct Space {
|
||||
/// Whether to include a layout for this space even if it would be empty.
|
||||
hard: bool,
|
||||
/// The so-far accumulated layouts.
|
||||
layouts: Vec<(BoxLayout, Gen2<GenAlign>)>,
|
||||
layouts: Vec<(BoxLayout, Gen<Align>)>,
|
||||
/// The full size of this space.
|
||||
size: Size,
|
||||
/// The used size of this space.
|
||||
@ -511,7 +487,7 @@ pub(super) struct Space {
|
||||
/// The remaining space.
|
||||
usable: Size,
|
||||
/// Which alignments for new boxes are still allowed.
|
||||
pub(super) allowed_align: GenAlign,
|
||||
pub(super) allowed_align: Align,
|
||||
}
|
||||
|
||||
impl Space {
|
||||
@ -523,7 +499,7 @@ impl Space {
|
||||
size,
|
||||
used: Size::ZERO,
|
||||
usable: size,
|
||||
allowed_align: GenAlign::Start,
|
||||
allowed_align: Align::Start,
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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)]
|
203
src/length.rs
203
src/length.rs
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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()),
|
||||
}),
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
20
src/paper.rs
20
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,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -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))),
|
||||
|
@ -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),
|
||||
|
@ -3,7 +3,7 @@
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use super::{is_newline, Scanner};
|
||||
use crate::length::Length;
|
||||
use crate::geom::Unit;
|
||||
use crate::syntax::token::*;
|
||||
use crate::syntax::{is_ident, Pos};
|
||||
|
||||
@ -279,8 +279,8 @@ fn parse_expr(text: &str) -> Token<'_> {
|
||||
Token::Float(num)
|
||||
} else if let Some(percent) = parse_percent(text) {
|
||||
Token::Percent(percent)
|
||||
} else if let Ok(length) = text.parse::<Length>() {
|
||||
Token::Length(length)
|
||||
} else if let Some((val, unit)) = parse_length(text) {
|
||||
Token::Length(val, unit)
|
||||
} else if is_ident(text) {
|
||||
Token::Ident(text)
|
||||
} else {
|
||||
@ -292,19 +292,41 @@ fn parse_percent(text: &str) -> Option<f64> {
|
||||
text.strip_suffix('%').and_then(|num| num.parse::<f64>().ok())
|
||||
}
|
||||
|
||||
fn parse_length(text: &str) -> Option<(f64, Unit)> {
|
||||
let len = text.len();
|
||||
|
||||
// We need at least some number and the unit.
|
||||
if len <= 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// We can view the string as bytes since a multibyte UTF-8 char cannot
|
||||
// have valid ASCII chars as subbytes.
|
||||
let split = len - 2;
|
||||
let bytes = text.as_bytes();
|
||||
let unit = match &bytes[split ..] {
|
||||
b"pt" => Unit::Pt,
|
||||
b"mm" => Unit::Mm,
|
||||
b"cm" => Unit::Cm,
|
||||
b"in" => Unit::In,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
text[.. split].parse::<f64>().ok().map(|val| (val, unit))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(non_snake_case)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::length::Length;
|
||||
use crate::parse::tests::check;
|
||||
|
||||
use Token::{
|
||||
BlockComment as BC, Bool, Chain, Float, Hex, Hyphen as Min, Ident as Id, Int,
|
||||
LeftBrace as LB, LeftBracket as L, LeftParen as LP, Length as Len,
|
||||
LineComment as LC, NonBreakingSpace as Nbsp, Percent, Plus, RightBrace as RB,
|
||||
RightBracket as R, RightParen as RP, Slash, Space as S, Star, Text as T, *,
|
||||
BlockComment as BC, Hyphen as Min, Ident as Id, LeftBrace as LB,
|
||||
LeftBracket as L, LeftParen as LP, LineComment as LC, NonBreakingSpace as Nbsp,
|
||||
RightBrace as RB, RightBracket as R, RightParen as RP, Space as S, Text as T, *,
|
||||
};
|
||||
use Unit::*;
|
||||
|
||||
fn Str(string: &str, terminated: bool) -> Token {
|
||||
Token::Str(TokenStr { string, terminated })
|
||||
@ -324,6 +346,16 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_length_from_str_parses_correct_value_and_unit() {
|
||||
assert_eq!(parse_length("2.5cm"), Some((2.5, Cm)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_length_from_str_works_with_non_ascii_chars() {
|
||||
assert_eq!(parse_length("123🚚"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokenize_whitespace() {
|
||||
t!(Body, "" => );
|
||||
@ -429,7 +461,7 @@ mod tests {
|
||||
t!(Header, "🌓, 🌍," => Invalid("🌓"), Comma, S(0), Invalid("🌍"), Comma);
|
||||
t!(Header, "{abc}" => LB, Id("abc"), RB);
|
||||
t!(Header, "(1,2)" => LP, Int(1), Comma, Int(2), RP);
|
||||
t!(Header, "12_pt, 12pt" => Invalid("12_pt"), Comma, S(0), Len(Length::pt(12.0)));
|
||||
t!(Header, "12_pt, 12pt" => Invalid("12_pt"), Comma, S(0), Length(12.0, Pt));
|
||||
t!(Header, "f: arg >> g" => Id("f"), Colon, S(0), Id("arg"), S(0), Chain, S(0), Id("g"));
|
||||
t!(Header, "=3.15" => Equals, Float(3.15));
|
||||
t!(Header, "arg, _b, _1" => Id("arg"), Comma, S(0), Id("_b"), Comma, S(0), Id("_1"));
|
||||
@ -446,9 +478,9 @@ mod tests {
|
||||
t!(Header, "12.3e5" => Float(12.3e5));
|
||||
t!(Header, "120%" => Percent(120.0));
|
||||
t!(Header, "12e4%" => Percent(120000.0));
|
||||
t!(Header, "1e5in" => Len(Length::inches(100000.0)));
|
||||
t!(Header, "2.3cm" => Len(Length::cm(2.3)));
|
||||
t!(Header, "02.4mm" => Len(Length::mm(2.4)));
|
||||
t!(Header, "1e5in" => Length(100000.0, In));
|
||||
t!(Header, "2.3cm" => Length(2.3, Cm));
|
||||
t!(Header, "02.4mm" => Length(2.4, Mm));
|
||||
t!(Header, "2.4.cm" => Invalid("2.4.cm"));
|
||||
t!(Header, "#6ae6dd" => Hex("6ae6dd"));
|
||||
t!(Header, "#8A083c" => Hex("8A083c"));
|
||||
@ -469,11 +501,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn tokenize_math() {
|
||||
t!(Header, "12e-3in" => Len(Length::inches(12e-3)));
|
||||
t!(Header, "12e-3in" => Length(12e-3, In));
|
||||
t!(Header, "-1" => Min, Int(1));
|
||||
t!(Header, "--1" => Min, Min, Int(1));
|
||||
t!(Header, "- 1" => Min, S(0), Int(1));
|
||||
t!(Header, "6.1cm + 4pt,a=1*2" => Len(Length::cm(6.1)), S(0), Plus, S(0), Len(Length::pt(4.0)),
|
||||
t!(Header, "6.1cm + 4pt,a=1*2" => Length(6.1, Cm), S(0), Plus, S(0), Length(4.0, Pt),
|
||||
Comma, Id("a"), Equals, Int(1), Star, Int(2));
|
||||
t!(Header, "(5 - 1) / 2.1" => LP, Int(5), S(0), Min, S(0), Int(1), RP,
|
||||
S(0), Slash, S(0), Float(2.1));
|
||||
|
@ -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};
|
||||
|
@ -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.
|
||||
|
@ -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),
|
||||
|
@ -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",
|
||||
|
@ -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),
|
||||
|
Loading…
x
Reference in New Issue
Block a user