From f8770d2b2a8ac389704897f92f2753398352835b Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 1 Oct 2020 15:35:09 +0200 Subject: [PATCH] =?UTF-8?q?Generalize=20layouting=20primitives=20?= =?UTF-8?q?=F0=9F=9B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/eval/value.rs | 63 ++++---- src/geom.rs | 62 ++++---- src/layout/line.rs | 32 ++-- src/layout/mod.rs | 70 +++++++-- src/layout/primitive.rs | 340 +++++++++++++++++++--------------------- src/layout/stack.rs | 92 +++++------ src/layout/tree.rs | 15 +- src/lib.rs | 11 +- src/library/align.rs | 20 +-- src/library/font.rs | 1 + src/library/spacing.rs | 6 +- src/prelude.rs | 12 +- 12 files changed, 372 insertions(+), 352 deletions(-) diff --git a/src/eval/value.rs b/src/eval/value.rs index cfbc302c2..56af4322d 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -43,18 +43,17 @@ impl Value { /// A natural-language name of the type of this expression, e.g. /// "identifier". pub fn name(&self) -> &'static str { - use Value::*; match self { - Ident(_) => "identifier", - Str(_) => "string", - Bool(_) => "bool", - Number(_) => "number", - Length(_) => "length", - Color(_) => "color", - Dict(_) => "dict", - Tree(_) => "syntax tree", - Func(_) => "function", - Commands(_) => "commands", + Self::Ident(_) => "identifier", + Self::Str(_) => "string", + Self::Bool(_) => "bool", + Self::Number(_) => "number", + Self::Length(_) => "length", + Self::Color(_) => "color", + Self::Dict(_) => "dict", + Self::Tree(_) => "syntax tree", + Self::Func(_) => "function", + Self::Commands(_) => "commands", } } } @@ -98,36 +97,34 @@ impl Spanned { impl Debug for Value { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - use Value::*; match self { - Ident(i) => i.fmt(f), - Str(s) => s.fmt(f), - Bool(b) => b.fmt(f), - Number(n) => n.fmt(f), - Length(s) => s.fmt(f), - Color(c) => c.fmt(f), - Dict(t) => t.fmt(f), - Tree(t) => t.fmt(f), - Func(_) => f.pad(""), - Commands(c) => c.fmt(f), + Self::Ident(i) => i.fmt(f), + Self::Str(s) => s.fmt(f), + Self::Bool(b) => b.fmt(f), + Self::Number(n) => n.fmt(f), + Self::Length(s) => s.fmt(f), + Self::Color(c) => c.fmt(f), + Self::Dict(t) => t.fmt(f), + Self::Tree(t) => t.fmt(f), + Self::Func(_) => f.pad(""), + Self::Commands(c) => c.fmt(f), } } } impl PartialEq for Value { fn eq(&self, other: &Self) -> bool { - use Value::*; match (self, other) { - (Ident(a), Ident(b)) => a == b, - (Str(a), Str(b)) => a == b, - (Bool(a), Bool(b)) => a == b, - (Number(a), Number(b)) => a == b, - (Length(a), Length(b)) => a == b, - (Color(a), Color(b)) => a == b, - (Dict(a), Dict(b)) => a == b, - (Tree(a), Tree(b)) => a == b, - (Func(a), Func(b)) => Rc::ptr_eq(a, b), - (Commands(a), Commands(b)) => a == b, + (Self::Ident(a), Self::Ident(b)) => a == b, + (Self::Str(a), Self::Str(b)) => a == b, + (Self::Bool(a), Self::Bool(b)) => a == b, + (Self::Number(a), Self::Number(b)) => a == b, + (Self::Length(a), Self::Length(b)) => a == b, + (Self::Color(a), Self::Color(b)) => a == b, + (Self::Dict(a), Self::Dict(b)) => a == b, + (Self::Tree(a), Self::Tree(b)) => a == b, + (Self::Func(a), Self::Func(b)) => Rc::ptr_eq(a, b), + (Self::Commands(a), Self::Commands(b)) => a == b, _ => false, } } diff --git a/src/geom.rs b/src/geom.rs index 925951155..f897c08fb 100644 --- a/src/geom.rs +++ b/src/geom.rs @@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter}; use std::ops::*; -use crate::layout::prelude::*; +use crate::layout::primitive::*; /// A value in two dimensions. #[derive(Default, Copy, Clone, Eq, PartialEq)] @@ -44,22 +44,22 @@ impl Value2 { /// Get the specificed component. pub fn get(self, axis: SpecAxis) -> T { match axis { - Horizontal => self.x, - Vertical => self.y, + SpecAxis::Horizontal => self.x, + SpecAxis::Vertical => self.y, } } /// Borrow the specificed component mutably. pub fn get_mut(&mut self, axis: SpecAxis) -> &mut T { match axis { - Horizontal => &mut self.x, - Vertical => &mut self.y, + SpecAxis::Horizontal => &mut self.x, + SpecAxis::Vertical => &mut self.y, } } /// Return the primary value of this specialized 2D-value. - pub fn primary(self, axes: LayoutAxes) -> T { - if axes.primary.axis() == Horizontal { + pub fn primary(self, sys: LayoutSystem) -> T { + if sys.primary.axis() == SpecAxis::Horizontal { self.x } else { self.y @@ -67,8 +67,8 @@ impl Value2 { } /// Borrow the primary value of this specialized 2D-value mutably. - pub fn primary_mut(&mut self, axes: LayoutAxes) -> &mut T { - if axes.primary.axis() == Horizontal { + pub fn primary_mut(&mut self, sys: LayoutSystem) -> &mut T { + if sys.primary.axis() == SpecAxis::Horizontal { &mut self.x } else { &mut self.y @@ -76,8 +76,8 @@ impl Value2 { } /// Return the secondary value of this specialized 2D-value. - pub fn secondary(self, axes: LayoutAxes) -> T { - if axes.primary.axis() == Horizontal { + pub fn secondary(self, sys: LayoutSystem) -> T { + if sys.primary.axis() == SpecAxis::Horizontal { self.y } else { self.x @@ -85,8 +85,8 @@ impl Value2 { } /// Borrow the secondary value of this specialized 2D-value mutably. - pub fn secondary_mut(&mut self, axes: LayoutAxes) -> &mut T { - if axes.primary.axis() == Horizontal { + pub fn secondary_mut(&mut self, sys: LayoutSystem) -> &mut T { + if sys.primary.axis() == SpecAxis::Horizontal { &mut self.y } else { &mut self.x @@ -94,22 +94,22 @@ impl Value2 { } /// Returns the generalized version of a `Size2D` dependent on the layouting - /// axes, that is: + /// system, that is: /// - `x` describes the primary axis instead of the horizontal one. /// - `y` describes the secondary axis instead of the vertical one. - pub fn generalized(self, axes: LayoutAxes) -> Self { - match axes.primary.axis() { - Horizontal => self, - Vertical => Self { x: self.y, y: self.x }, + pub fn generalized(self, sys: LayoutSystem) -> Self { + match sys.primary.axis() { + SpecAxis::Horizontal => self, + SpecAxis::Vertical => Self { x: self.y, y: self.x }, } } /// Returns the specialized version of this generalized Size2D (inverse to /// `generalized`). - pub fn specialized(self, axes: LayoutAxes) -> Self { + pub fn specialized(self, sys: LayoutSystem) -> Self { // In fact, generalized is its own inverse. For reasons of clarity // at the call site, we still have this second function. - self.generalized(axes) + self.generalized(sys) } /// Swap the `x` and `y` values. @@ -158,19 +158,19 @@ impl Size { /// /// This assumes the size to be generalized such that `x` corresponds to the /// primary axis. - pub fn anchor(self, align: LayoutAlign, axes: LayoutAxes) -> Self { + pub fn anchor(self, align: LayoutAlign, sys: LayoutSystem) -> Self { Size { - x: anchor(self.x, align.primary, axes.primary), - y: anchor(self.y, align.secondary, axes.secondary), + x: anchor(self.x, align.primary, sys.primary), + y: anchor(self.y, align.secondary, sys.secondary), } } } fn anchor(length: f64, align: GenAlign, dir: Dir) -> f64 { match (dir.is_positive(), align) { - (true, Start) | (false, End) => 0.0, - (_, Center) => length / 2.0, - (true, End) | (false, Start) => length, + (true, GenAlign::Start) | (false, GenAlign::End) => 0.0, + (_, GenAlign::Center) => length / 2.0, + (true, GenAlign::End) | (false, GenAlign::Start) => length, } } @@ -216,15 +216,15 @@ impl Value4 { /// /// Center alignment is treated the same as origin alignment. pub fn get_mut(&mut self, mut dir: Dir, align: GenAlign) -> &mut T { - if align == End { + if align == GenAlign::End { dir = dir.inv(); } match dir { - LTR => &mut self.left, - RTL => &mut self.right, - TTB => &mut self.top, - BTT => &mut self.bottom, + Dir::LTR => &mut self.left, + Dir::RTL => &mut self.right, + Dir::TTB => &mut self.top, + Dir::BTT => &mut self.bottom, } } diff --git a/src/layout/line.rs b/src/layout/line.rs index 26b3d6a40..d8e97df36 100644 --- a/src/layout/line.rs +++ b/src/layout/line.rs @@ -24,8 +24,8 @@ pub struct LineLayouter { pub struct LineContext { /// The spaces to layout into. pub spaces: LayoutSpaces, - /// The initial layouting axes, which can be updated through `set_axes`. - pub axes: LayoutAxes, + /// The initial layouting system, which can be updated through `set_sys`. + pub sys: LayoutSystem, /// The alignment of the _resulting_ layout. This does not effect the line /// layouting itself, but rather how the finished layout will be positioned /// in a parent layout. @@ -64,7 +64,7 @@ impl LineLayouter { Self { stack: StackLayouter::new(StackContext { spaces: ctx.spaces.clone(), - axes: ctx.axes, + sys: ctx.sys, align: ctx.align, repeat: ctx.repeat, }), @@ -75,7 +75,7 @@ impl LineLayouter { /// Add a layout. pub fn add(&mut self, layout: BoxLayout) { - let axes = self.ctx.axes; + let sys = self.ctx.sys; if let Some(align) = self.run.align { if layout.align.secondary != align.secondary { @@ -92,7 +92,7 @@ impl LineLayouter { } else if layout.align.primary > align.primary { let mut rest_run = LineRun::new(); - let usable = self.stack.usable().primary(axes); + let usable = self.stack.usable().primary(sys); rest_run.usable = Some(match layout.align.primary { GenAlign::Start => unreachable!("start > x"), GenAlign::Center => usable - 2.0 * self.run.size.x, @@ -112,7 +112,7 @@ impl LineLayouter { self.add_primary_spacing(spacing, SpacingKind::Hard); } - let size = layout.size.generalized(axes); + let size = layout.size.generalized(sys); if !self.usable().fits(size) { if !self.line_is_empty() { @@ -148,7 +148,7 @@ impl LineLayouter { /// needed. fn usable(&self) -> Size { // The base is the usable space of the stack layouter. - let mut usable = self.stack.usable().generalized(self.ctx.axes); + let mut usable = self.stack.usable().generalized(self.ctx.sys); // If there was another run already, override the stack's size. if let Some(primary) = self.run.usable { @@ -190,11 +190,11 @@ impl LineLayouter { self.stack.add_spacing(spacing, kind) } - /// Update the layouting axes. - pub fn set_axes(&mut self, axes: LayoutAxes) { + /// Update the layouting system. + pub fn set_sys(&mut self, sys: LayoutSystem) { self.finish_line_if_not_empty(); - self.ctx.axes = axes; - self.stack.set_axes(axes) + self.ctx.sys = sys; + self.stack.set_sys(sys) } /// Update the layouting spaces. @@ -215,7 +215,7 @@ impl LineLayouter { /// it will fit into this layouter's underlying stack. pub fn remaining(&self) -> LayoutSpaces { let mut spaces = self.stack.remaining(); - *spaces[0].size.secondary_mut(self.ctx.axes) -= self.run.size.y; + *spaces[0].size.secondary_mut(self.ctx.sys) -= self.run.size.y; spaces } @@ -244,9 +244,9 @@ impl LineLayouter { let layouts = std::mem::take(&mut self.run.layouts); for (offset, layout) in layouts { - let x = match self.ctx.axes.primary.is_positive() { + let x = match self.ctx.sys.primary.is_positive() { true => offset, - false => self.run.size.x - offset - layout.size.primary(self.ctx.axes), + false => self.run.size.x - offset - layout.size.primary(self.ctx.sys), }; let pos = Size::with_x(x); @@ -254,8 +254,8 @@ impl LineLayouter { } self.stack.add(BoxLayout { - size: self.run.size.specialized(self.ctx.axes), - align: self.run.align.unwrap_or(LayoutAlign::new(Start, Start)), + size: self.run.size.specialized(self.ctx.sys), + align: self.run.align.unwrap_or(LayoutAlign::START), elements, }); diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 0843e80ac..4fdbdde1b 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -7,17 +7,6 @@ pub mod stack; pub mod text; mod tree; -/// Basic types used across the layouting engine. -pub mod prelude { - pub use super::primitive::*; - pub use super::{layout, BoxLayout, LayoutContext, LayoutSpace, MultiLayout}; - pub use Dir::*; - pub use GenAlign::*; - pub use GenAxis::*; - pub use SpecAlign::*; - pub use SpecAxis::*; -} - pub use primitive::*; pub use tree::layout_tree as layout; @@ -28,7 +17,6 @@ use crate::style::{LayoutStyle, PageStyle, TextStyle}; use crate::syntax::SynTree; use elements::LayoutElements; -use prelude::*; /// A collection of layouts. pub type MultiLayout = Vec; @@ -60,8 +48,8 @@ pub struct LayoutContext<'a> { /// Whether to spill over into copies of the last space or finish layouting /// when the last space is used up. pub repeat: bool, - /// The axes along which content is laid out. - pub axes: LayoutAxes, + /// The system into which content is laid out. + pub sys: LayoutSystem, /// The alignment of the _resulting_ layout. This does not effect the line /// layouting itself, but rather how the finished layout will be positioned /// in a parent layout. @@ -146,7 +134,57 @@ pub enum Command { /// Update the alignment for future boxes added to this layouting process. SetAlignment(LayoutAlign), - /// Update the layouting axes along which future boxes will be laid + /// Update the layouting system along which future boxes will be laid /// out. This ends the current line. - SetAxes(LayoutAxes), + SetSystem(LayoutSystem), +} + +/// Defines how spacing interacts with surrounding spacing. +/// +/// There are two options for interaction: Hard and soft spacing. Typically, +/// hard spacing is used when a fixed amount of space needs to be inserted no +/// matter what. In contrast, soft spacing can be used to insert a default +/// spacing between e.g. two words or paragraphs that can still be overridden by +/// a hard space. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum SpacingKind { + /// Hard spaces are always laid out and consume surrounding soft space. + Hard, + /// Soft spaces are not laid out if they are touching a hard space and + /// consume neighbouring soft spaces with higher levels. + Soft(u32), +} + +impl SpacingKind { + /// The standard spacing kind used for paragraph spacing. + pub const PARAGRAPH: Self = Self::Soft(1); + + /// The standard spacing kind used for line spacing. + pub const LINE: Self = Self::Soft(2); + + /// The standard spacing kind used for word spacing. + pub const WORD: Self = Self::Soft(1); +} + +/// The spacing kind of the most recently inserted item in a layouting process. +/// +/// Since the last inserted item may not be spacing at all, this can be `None`. +#[derive(Debug, Copy, Clone, PartialEq)] +enum LastSpacing { + /// The last item was hard spacing. + Hard, + /// The last item was soft spacing with the given width and level. + Soft(f64, u32), + /// The last item wasn't spacing. + None, +} + +impl LastSpacing { + /// The width of the soft space if this is a soft space or zero otherwise. + fn soft_or_zero(self) -> f64 { + match self { + LastSpacing::Soft(space, _) => space, + _ => 0.0, + } + } } diff --git a/src/layout/primitive.rs b/src/layout/primitive.rs index 6b5df7db7..cdbe63c2d 100644 --- a/src/layout/primitive.rs +++ b/src/layout/primitive.rs @@ -2,46 +2,33 @@ use std::fmt::{self, Display, Formatter}; -use super::prelude::*; +/// Specifies the directions into which content is laid out. +/// +/// The primary component defines into which direction text and lines flow and the +/// secondary into which paragraphs and pages grow. +pub type LayoutSystem = Gen2; -/// Specifies the axes along content is laid out. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct LayoutAxes { - pub primary: Dir, - pub secondary: Dir, -} - -impl LayoutAxes { - /// Create a new instance from the two directions. - /// - /// # Panics - /// This function panics if the directions are aligned, i.e. if they are - /// on the same axis. - pub fn new(primary: Dir, secondary: Dir) -> Self { - if primary.axis() == secondary.axis() { - panic!("directions {} and {} are aligned", primary, secondary); - } - Self { primary, secondary } - } - - /// Return the direction of the specified generic axis. - pub fn get(self, axis: GenAxis) -> Dir { - match axis { - Primary => self.primary, - Secondary => self.secondary, - } - } - - /// Borrow the direction of the specified generic axis mutably. - pub fn get_mut(&mut self, axis: GenAxis) -> &mut Dir { - match axis { - Primary => &mut self.primary, - Secondary => &mut self.secondary, - } +impl Default for LayoutSystem { + fn default() -> Self { + Self::new(Dir::LTR, Dir::TTB) } } -/// Directions along which content is laid out. +/// Specifies where to align a layout in a parent container. +pub type LayoutAlign = Gen2; + +impl LayoutAlign { + /// The layout alignment that has both components set to `Start`. + pub const START: Self = Self { + primary: GenAlign::Start, + secondary: GenAlign::Start, + }; +} + +/// Whether to expand a layout to an area's full size or shrink it to fit its content. +pub type LayoutExpansion = Spec2; + +/// The four directions into which content can be laid out. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Dir { /// Left to right. @@ -55,11 +42,31 @@ pub enum Dir { } 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 { - LTR | RTL => Horizontal, - TTB | BTT => Vertical, + Self::LTR | Self::RTL => SpecAxis::Horizontal, + Self::TTB | Self::BTT => SpecAxis::Vertical, } } @@ -68,8 +75,8 @@ impl Dir { /// The positive directions are left-to-right and top-to-bottom. pub fn is_positive(self) -> bool { match self { - LTR | TTB => true, - RTL | BTT => false, + Self::LTR | Self::TTB => true, + Self::RTL | Self::BTT => false, } } @@ -84,10 +91,10 @@ impl Dir { /// The inverse direction. pub fn inv(self) -> Self { match self { - LTR => RTL, - RTL => LTR, - TTB => BTT, - BTT => TTB, + Self::LTR => Self::RTL, + Self::RTL => Self::LTR, + Self::TTB => Self::BTT, + Self::BTT => Self::TTB, } } } @@ -95,10 +102,10 @@ impl Dir { impl Display for Dir { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.pad(match self { - LTR => "ltr", - RTL => "rtl", - TTB => "ttb", - BTT => "btt", + Self::LTR => "ltr", + Self::RTL => "rtl", + Self::TTB => "ttb", + Self::BTT => "btt", }) } } @@ -106,24 +113,24 @@ impl Display for Dir { /// The two generic layouting axes. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum GenAxis { - /// The primary layouting direction along which text and lines flow. + /// The primary layouting direction into which text and lines flow. Primary, - /// The secondary layouting direction along which paragraphs grow. + /// The secondary layouting direction into which paragraphs grow. Secondary, } impl GenAxis { - /// The specific version of this axis in the given system of axes. - pub fn to_specific(self, axes: LayoutAxes) -> SpecAxis { - axes.get(self).axis() + /// The specific version of this axis in the given layout system. + pub fn to_spec(self, sys: LayoutSystem) -> SpecAxis { + sys.get(self).axis() } } impl Display for GenAxis { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.pad(match self { - Primary => "primary", - Secondary => "secondary", + Self::Primary => "primary", + Self::Secondary => "secondary", }) } } @@ -138,12 +145,12 @@ pub enum SpecAxis { } impl SpecAxis { - /// The generic version of this axis in the given system of axes. - pub fn to_generic(self, axes: LayoutAxes) -> GenAxis { - if self == axes.primary.axis() { - Primary + /// The generic version of this axis in the given layout system. + pub fn to_gen(self, sys: LayoutSystem) -> GenAxis { + if self == sys.primary.axis() { + GenAxis::Primary } else { - Secondary + GenAxis::Secondary } } } @@ -151,40 +158,19 @@ impl SpecAxis { impl Display for SpecAxis { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.pad(match self { - Horizontal => "horizontal", - Vertical => "vertical", + Self::Horizontal => "horizontal", + Self::Vertical => "vertical", }) } } -/// Specifies where to align a layout in a parent container. +/// A side of a container. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct LayoutAlign { - pub primary: GenAlign, - pub secondary: GenAlign, -} - -impl LayoutAlign { - /// Create a new instance from the two alignments. - pub fn new(primary: GenAlign, secondary: GenAlign) -> Self { - Self { primary, secondary } - } - - /// Return the alignment for the specified generic axis. - pub fn get(self, axis: GenAxis) -> GenAlign { - match axis { - Primary => self.primary, - Secondary => self.secondary, - } - } - - /// Borrow the alignment for the specified generic axis mutably. - pub fn get_mut(&mut self, axis: GenAxis) -> &mut GenAlign { - match axis { - Primary => &mut self.primary, - Secondary => &mut self.secondary, - } - } +pub enum Side { + Left, + Top, + Right, + Bottom, } /// Where to align content along an axis in a generic context. @@ -199,9 +185,9 @@ impl GenAlign { /// The inverse alignment. pub fn inv(self) -> Self { match self { - Start => End, - Center => Center, - End => Start, + Self::Start => Self::End, + Self::Center => Self::Center, + Self::End => Self::Start, } } } @@ -209,9 +195,9 @@ impl GenAlign { impl Display for GenAlign { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.pad(match self { - Start => "start", - Center => "center", - End => "end", + Self::Start => "start", + Self::Center => "center", + Self::End => "end", }) } } @@ -232,30 +218,29 @@ impl SpecAlign { /// Returns `None` if this is `Center` since the axis is unknown. pub fn axis(self) -> Option { match self { - Self::Left => Some(Horizontal), - Self::Right => Some(Horizontal), - Self::Top => Some(Vertical), - Self::Bottom => Some(Vertical), + Self::Left => Some(SpecAxis::Horizontal), + Self::Right => Some(SpecAxis::Horizontal), + Self::Top => Some(SpecAxis::Vertical), + Self::Bottom => Some(SpecAxis::Vertical), Self::Center => None, } } - /// The generic version of this alignment in the given system of axes. - pub fn to_generic(self, axes: LayoutAxes) -> GenAlign { - let get = |spec: SpecAxis, align: GenAlign| { - let axis = spec.to_generic(axes); - if axes.get(axis).is_positive() { - align + /// The generic version of this alignment in the given layout system. + pub fn to_gen(self, sys: LayoutSystem) -> GenAlign { + let get = |spec: SpecAxis, positive: GenAlign| { + if sys.get(spec.to_gen(sys)).is_positive() { + positive } else { - align.inv() + positive.inv() } }; match self { - Self::Left => get(Horizontal, Start), - Self::Right => get(Horizontal, End), - Self::Top => get(Vertical, Start), - Self::Bottom => get(Vertical, End), + Self::Left => get(SpecAxis::Horizontal, GenAlign::Start), + Self::Right => get(SpecAxis::Horizontal, GenAlign::End), + Self::Top => get(SpecAxis::Vertical, GenAlign::Start), + Self::Bottom => get(SpecAxis::Vertical, GenAlign::End), Self::Center => GenAlign::Center, } } @@ -273,85 +258,82 @@ impl Display for SpecAlign { } } -/// Specifies whether to expand a layout to the full size of the space it is -/// laid out in or to shrink it to fit the content. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct LayoutExpansion { - /// Whether to expand on the horizontal axis. - pub horizontal: bool, - /// Whether to expand on the vertical axis. - pub vertical: bool, +/// A generic container with two components for the two generic axes. +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] +pub struct Gen2 { + /// The primary component. + pub primary: T, + /// The secondary component. + pub secondary: T, } -impl LayoutExpansion { - /// Create a new instance from the two values. - pub fn new(horizontal: bool, vertical: bool) -> Self { +impl Gen2 { + /// Create a new instance from the two components. + pub fn new(primary: T, secondary: T) -> Self { + Self { primary, secondary } + } + + /// Return the component for the specified generic axis. + pub fn get(self, axis: GenAxis) -> T { + match axis { + GenAxis::Primary => self.primary, + GenAxis::Secondary => self.secondary, + } + } + + /// Borrow the component for the specified generic axis. + pub fn get_ref(&mut self, axis: GenAxis) -> &T { + match axis { + GenAxis::Primary => &mut self.primary, + GenAxis::Secondary => &mut self.secondary, + } + } + + /// Borrow the component for the specified generic axis mutably. + pub fn get_mut(&mut self, axis: GenAxis) -> &mut T { + match axis { + GenAxis::Primary => &mut self.primary, + GenAxis::Secondary => &mut self.secondary, + } + } +} + +/// A generic container with two components for the two specific axes. +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] +pub struct Spec2 { + /// The horizontal component. + pub horizontal: T, + /// The vertical component. + pub vertical: T, +} + +impl Spec2 { + /// Create a new instance from the two components. + pub fn new(horizontal: T, vertical: T) -> Self { Self { horizontal, vertical } } - /// Return the expansion value for the given specific axis. - pub fn get(self, axis: SpecAxis) -> bool { + /// Return the component for the given specific axis. + pub fn get(self, axis: SpecAxis) -> T { match axis { - Horizontal => self.horizontal, - Vertical => self.vertical, + SpecAxis::Horizontal => self.horizontal, + SpecAxis::Vertical => self.vertical, } } - /// Borrow the expansion value for the given specific axis mutably. - pub fn get_mut(&mut self, axis: SpecAxis) -> &mut bool { + /// Borrow the component for the given specific axis. + pub fn get_ref(&mut self, axis: SpecAxis) -> &T { match axis { - Horizontal => &mut self.horizontal, - Vertical => &mut self.vertical, - } - } -} - -/// Defines how spacing interacts with surrounding spacing. -/// -/// There are two options for interaction: Hard and soft spacing. Typically, -/// hard spacing is used when a fixed amount of space needs to be inserted no -/// matter what. In contrast, soft spacing can be used to insert a default -/// spacing between e.g. two words or paragraphs that can still be overridden by -/// a hard space. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum SpacingKind { - /// Hard spaces are always laid out and consume surrounding soft space. - Hard, - /// Soft spaces are not laid out if they are touching a hard space and - /// consume neighbouring soft spaces with higher levels. - Soft(u32), -} - -impl SpacingKind { - /// The standard spacing kind used for paragraph spacing. - pub const PARAGRAPH: Self = Self::Soft(1); - - /// The standard spacing kind used for line spacing. - pub const LINE: Self = Self::Soft(2); - - /// The standard spacing kind used for word spacing. - pub const WORD: Self = Self::Soft(1); -} - -/// The spacing kind of the most recently inserted item in a layouting process. -/// -/// Since the last inserted item may not be spacing at all, this can be `None`. -#[derive(Debug, Copy, Clone, PartialEq)] -pub(crate) enum LastSpacing { - /// The last item was hard spacing. - Hard, - /// The last item was soft spacing with the given width and level. - Soft(f64, u32), - /// The last item wasn't spacing. - None, -} - -impl LastSpacing { - /// The width of the soft space if this is a soft space or zero otherwise. - pub fn soft_or_zero(self) -> f64 { - match self { - LastSpacing::Soft(space, _) => space, - _ => 0.0, + SpecAxis::Horizontal => &mut self.horizontal, + SpecAxis::Vertical => &mut self.vertical, + } + } + + /// Borrow the component for the given specific axis mutably. + pub fn get_mut(&mut self, axis: SpecAxis) -> &mut T { + match axis { + SpecAxis::Horizontal => &mut self.horizontal, + SpecAxis::Vertical => &mut self.vertical, } } } diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 73e246e2c..f88184635 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -35,8 +35,8 @@ pub struct StackLayouter { pub struct StackContext { /// The spaces to layout into. pub spaces: LayoutSpaces, - /// The initial layouting axes, which can be updated through `set_axes`. - pub axes: LayoutAxes, + /// The initial layouting system, which can be updated through `set_sys`. + pub sys: LayoutSystem, /// The alignment of the _resulting_ layout. This does not effect the line /// layouting itself, but rather how the finished layout will be positioned /// in a parent layout. @@ -46,7 +46,7 @@ pub struct StackContext { pub repeat: bool, } -/// A layout space composed of subspaces which can have different axes and +/// A layout space composed of subspaces which can have different systems and /// alignments. struct Space { /// The index of this space in `ctx.spaces`. @@ -54,7 +54,7 @@ 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<(LayoutAxes, BoxLayout)>, + layouts: Vec<(LayoutSystem, BoxLayout)>, /// The specialized size of this space. size: Size, /// The specialized remaining space. @@ -103,11 +103,11 @@ impl StackLayouter { } // Change the usable space and size of the space. - self.update_metrics(layout.size.generalized(self.ctx.axes)); + self.update_metrics(layout.size.generalized(self.ctx.sys)); // Add the box to the vector and remember that spacings are allowed // again. - self.space.layouts.push((self.ctx.axes, layout)); + self.space.layouts.push((self.ctx.sys, layout)); self.space.last_spacing = LastSpacing::None; } @@ -126,13 +126,13 @@ impl StackLayouter { // A hard space is simply an empty box. SpacingKind::Hard => { // Reduce the spacing such that it definitely fits. - spacing = spacing.min(self.space.usable.secondary(self.ctx.axes)); + spacing = spacing.min(self.space.usable.secondary(self.ctx.sys)); let size = Size::with_y(spacing); self.update_metrics(size); - self.space.layouts.push((self.ctx.axes, BoxLayout { - size: size.specialized(self.ctx.axes), - align: LayoutAlign::new(Start, Start), + self.space.layouts.push((self.ctx.sys, BoxLayout { + size: size.specialized(self.ctx.sys), + align: LayoutAlign::START, elements: LayoutElements::new(), })); @@ -156,10 +156,10 @@ impl StackLayouter { } fn update_metrics(&mut self, added: Size) { - let axes = self.ctx.axes; + let sys = self.ctx.sys; - let mut size = self.space.size.generalized(axes); - let mut extra = self.space.extra.generalized(axes); + let mut size = self.space.size.generalized(sys); + let mut extra = self.space.extra.generalized(sys); size.x += (added.x - extra.x).max(0.0); size.y += (added.y - extra.y).max(0.0); @@ -167,16 +167,17 @@ impl StackLayouter { extra.x = extra.x.max(added.x); extra.y = (extra.y - added.y).max(0.0); - self.space.size = size.specialized(axes); - self.space.extra = extra.specialized(axes); - *self.space.usable.secondary_mut(axes) -= added.y; + self.space.size = size.specialized(sys); + self.space.extra = extra.specialized(sys); + *self.space.usable.secondary_mut(sys) -= added.y; } /// Returns true if a space break is necessary. fn update_rulers(&mut self, align: LayoutAlign) -> bool { let allowed = self.is_fitting_alignment(align); if allowed { - *self.space.rulers.get_mut(self.ctx.axes.secondary, Start) = align.secondary; + *self.space.rulers.get_mut(self.ctx.sys.secondary, GenAlign::Start) = + align.secondary; } allowed } @@ -184,23 +185,23 @@ impl StackLayouter { /// Whether a layout with the given alignment can still be layouted into the /// active space or a space break is necessary. pub(crate) fn is_fitting_alignment(&mut self, align: LayoutAlign) -> bool { - self.is_fitting_axis(self.ctx.axes.primary, align.primary) - && self.is_fitting_axis(self.ctx.axes.secondary, align.secondary) + self.is_fitting_axis(self.ctx.sys.primary, align.primary) + && self.is_fitting_axis(self.ctx.sys.secondary, align.secondary) } fn is_fitting_axis(&mut self, dir: Dir, align: GenAlign) -> bool { - align >= *self.space.rulers.get_mut(dir, Start) - && align <= self.space.rulers.get_mut(dir, End).inv() + align >= *self.space.rulers.get_mut(dir, GenAlign::Start) + && align <= self.space.rulers.get_mut(dir, GenAlign::End).inv() } - /// Update the layouting axes. - pub fn set_axes(&mut self, axes: LayoutAxes) { + /// Update the layouting system. + pub fn set_sys(&mut self, sys: LayoutSystem) { // Forget the spacing because it is not relevant anymore. - if axes.secondary != self.ctx.axes.secondary { + if sys.secondary != self.ctx.sys.secondary { self.space.last_spacing = LastSpacing::Hard; } - self.ctx.axes = axes; + self.ctx.sys = sys; } /// Update the layouting spaces. @@ -253,7 +254,7 @@ impl StackLayouter { pub fn usable(&self) -> Size { self.space.usable - Size::with_y(self.space.last_spacing.soft_or_zero()) - .specialized(self.ctx.axes) + .specialized(self.ctx.sys) } /// Whether the current layout space is empty. @@ -308,7 +309,7 @@ impl StackLayouter { bottom: start.y + self.space.size.y, }; - for (axes, layout) in &self.space.layouts { + for (sys, layout) in &self.space.layouts { // First, we store the bounds calculated so far (which were reduced // by the predecessors of this layout) as the initial bounding box // of this layout. @@ -318,8 +319,8 @@ impl StackLayouter { // layout uses up space from the origin to the end. Thus, it reduces // the usable space for following layouts at it's origin by its // extent along the secondary axis. - *bound.get_mut(axes.secondary, Start) += - axes.secondary.factor() * layout.size.secondary(*axes); + *bound.get_mut(sys.secondary, GenAlign::Start) += + sys.secondary.factor() * layout.size.secondary(*sys); } // ------------------------------------------------------------------ // @@ -329,28 +330,29 @@ impl StackLayouter { // The `x` field stores the maximal primary extent in one axis-aligned // run, while the `y` fields stores the accumulated secondary extent. let mut extent = Size::ZERO; - let mut rotation = Vertical; + let mut rotation = SpecAxis::Vertical; for (bound, entry) in bounds.iter_mut().zip(&self.space.layouts).rev() { - let (axes, layout) = entry; + let (sys, layout) = entry; - // When the axes are rotated, the the maximal primary size - // (`extent.x`) dictates how much secondary extent the whole run - // had. This value is thus stored in `extent.y`. The primary extent - // is reset for this new axis-aligned run. - if rotation != axes.secondary.axis() { + // When the axes are rotated, the maximal primary size (`extent.x`) + // dictates how much secondary extent the whole run had. This value + // is thus stored in `extent.y`. The primary extent is reset for + // this new axis-aligned run. + if rotation != sys.secondary.axis() { extent.y = extent.x; extent.x = 0.0; - rotation = axes.secondary.axis(); + rotation = sys.secondary.axis(); } // We reduce the bounding box of this layout at it's end by the // accumulated secondary extent of all layouts we have seen so far, // which are the layouts after this one since we iterate reversed. - *bound.get_mut(axes.secondary, End) -= axes.secondary.factor() * extent.y; + *bound.get_mut(sys.secondary, GenAlign::End) -= + sys.secondary.factor() * extent.y; // Then, we add this layout's secondary extent to the accumulator. - let size = layout.size.generalized(*axes); + let size = layout.size.generalized(*sys); extent.x = extent.x.max(size.x); extent.y += size.y; } @@ -362,17 +364,17 @@ impl StackLayouter { let mut elements = LayoutElements::new(); let layouts = std::mem::take(&mut self.space.layouts); - for ((axes, layout), bound) in layouts.into_iter().zip(bounds) { - let size = layout.size.specialized(axes); + for ((sys, layout), bound) in layouts.into_iter().zip(bounds) { + let size = layout.size.specialized(sys); let align = layout.align; // The space in which this layout is aligned is given by the // distances between the borders of it's bounding box. let usable = Size::new(bound.right - bound.left, bound.bottom - bound.top) - .generalized(axes); + .generalized(sys); - let local = usable.anchor(align, axes) - size.anchor(align, axes); - let pos = Size::new(bound.left, bound.top) + local.specialized(axes); + let local = usable.anchor(align, sys) - size.anchor(align, sys); + let pos = Size::new(bound.left, bound.top) + local.specialized(sys); elements.extend_offset(pos, layout.elements); } @@ -404,7 +406,7 @@ impl Space { size: Size::ZERO, usable, extra: Size::ZERO, - rulers: Value4::with_all(Start), + rulers: Value4::with_all(GenAlign::Start), last_spacing: LastSpacing::Hard, } } diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 14ff4630e..f8e4160ca 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -29,7 +29,7 @@ impl<'a> TreeLayouter<'a> { Self { layouter: LineLayouter::new(LineContext { spaces: ctx.spaces.clone(), - axes: ctx.axes, + sys: ctx.sys, align: ctx.align, repeat: ctx.repeat, line_spacing: ctx.style.text.line_spacing(), @@ -107,7 +107,7 @@ impl<'a> TreeLayouter<'a> { layout_text(text, TextContext { loader: &mut self.ctx.loader.borrow_mut(), style: &self.style.text, - dir: self.ctx.axes.primary, + dir: self.ctx.sys.primary, align: self.ctx.align, }) .await, @@ -170,15 +170,14 @@ impl<'a> TreeLayouter<'a> { async fn execute_command(&mut self, command: Command, span: Span) { use Command::*; - match command { LayoutSyntaxTree(tree) => self.layout_tree(&tree).await, Add(layout) => self.layouter.add(layout), AddMultiple(layouts) => self.layouter.add_multiple(layouts), AddSpacing(space, kind, axis) => match axis { - Primary => self.layouter.add_primary_spacing(space, kind), - Secondary => self.layouter.add_secondary_spacing(space, kind), + GenAxis::Primary => self.layouter.add_primary_spacing(space, kind), + GenAxis::Secondary => self.layouter.add_secondary_spacing(space, kind), }, BreakLine => self.layouter.finish_line(), @@ -223,9 +222,9 @@ impl<'a> TreeLayouter<'a> { } SetAlignment(align) => self.ctx.align = align, - SetAxes(axes) => { - self.layouter.set_axes(axes); - self.ctx.axes = axes; + SetSystem(sys) => { + self.layouter.set_sys(sys); + self.ctx.sys = sys; } } } diff --git a/src/lib.rs b/src/lib.rs index 431525426..8bf5398d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,7 +47,10 @@ use std::pin::Pin; use crate::diagnostic::Diagnostic; use crate::eval::{Scope, Value}; use crate::font::SharedFontLoader; -use crate::layout::{Commands, MultiLayout}; +use crate::layout::{ + layout, Commands, Dir, GenAlign, LayoutAlign, LayoutContext, LayoutExpansion, + LayoutSpace, LayoutSystem, MultiLayout, +}; use crate::style::{LayoutStyle, PageStyle, TextStyle}; use crate::syntax::{Decoration, Offset, Pos, SpanVec, SynTree}; @@ -90,8 +93,6 @@ impl Typesetter { /// Layout a syntax tree and return the produced layout. pub async fn layout(&self, tree: &SynTree) -> Pass { - use crate::layout::prelude::*; - let margins = self.style.page.margins(); layout(&tree, LayoutContext { loader: &self.loader, @@ -104,8 +105,8 @@ impl Typesetter { expansion: LayoutExpansion::new(true, true), }], repeat: true, - axes: LayoutAxes::new(LTR, TTB), - align: LayoutAlign::new(Start, Start), + sys: LayoutSystem::new(Dir::LTR, Dir::TTB), + align: LayoutAlign::new(GenAlign::Start, GenAlign::Start), root: true, }) .await diff --git a/src/library/align.rs b/src/library/align.rs index c909087a9..350724f7a 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -23,8 +23,8 @@ pub async fn align(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass let all = args .take_all_num_vals::>() .map(|align| (align.v.axis(), align)) - .chain(h.into_iter().map(|align| (Some(Horizontal), align))) - .chain(v.into_iter().map(|align| (Some(Vertical), align))); + .chain(h.into_iter().map(|align| (Some(SpecAxis::Horizontal), align))) + .chain(v.into_iter().map(|align| (Some(SpecAxis::Vertical), align))); let mut aligns = ctx.align; let mut had = [false; 2]; @@ -43,8 +43,8 @@ pub async fn align(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass } else if had[axis as usize] { error!(@f, align.span, "duplicate alignment for {} axis", axis); } else { - let gen_align = align.v.to_generic(ctx.axes); - *aligns.get_mut(axis.to_generic(ctx.axes)) = gen_align; + let gen_align = align.v.to_gen(ctx.sys); + *aligns.get_mut(axis.to_gen(ctx.sys)) = gen_align; had[axis as usize] = true; } } else { @@ -54,7 +54,7 @@ pub async fn align(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass // We have two unflushed centers, meaning we know that both axes // are to be centered. had = [true, true]; - aligns = LayoutAlign::new(Center, Center); + aligns = LayoutAlign::new(GenAlign::Center, GenAlign::Center); } else { deferred_center = true; } @@ -63,13 +63,13 @@ pub async fn align(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass // Flush a deferred center alignment if we know have had at least one // known alignment. if deferred_center && had != [false, false] { - let axis = if !had[Horizontal as usize] { - Horizontal + let axis = if !had[SpecAxis::Horizontal as usize] { + SpecAxis::Horizontal } else { - Vertical + SpecAxis::Vertical }; - *aligns.get_mut(axis.to_generic(ctx.axes)) = Center; + *aligns.get_mut(axis.to_gen(ctx.sys)) = GenAlign::Center; had[axis as usize] = true; deferred_center = false; @@ -79,7 +79,7 @@ pub async fn align(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass // If center has not been flushed by known, it is the only argument and then // we default to applying it to the primary axis. if deferred_center { - aligns.primary = Center; + aligns.primary = GenAlign::Center; } let commands = match content { diff --git a/src/library/font.rs b/src/library/font.rs index e01338223..1ffc50a85 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -1,6 +1,7 @@ use fontdock::{FontStretch, FontStyle, FontWeight}; use super::*; +use crate::eval::StringLike; use crate::length::ScaleLength; /// `font`: Configure the font. diff --git a/src/library/spacing.rs b/src/library/spacing.rs index aa2712d3b..6596717bd 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -7,7 +7,7 @@ use crate::length::ScaleLength; /// # Positional arguments /// - The spacing (length or relative to font size). pub async fn h(name: Span, args: DictValue, ctx: LayoutContext<'_>) -> Pass { - spacing(name, args, ctx, Horizontal) + spacing(name, args, ctx, SpecAxis::Horizontal) } /// `v`: Add vertical spacing. @@ -15,7 +15,7 @@ pub async fn h(name: Span, args: DictValue, ctx: LayoutContext<'_>) -> Pass) -> Pass { - spacing(name, args, ctx, Vertical) + spacing(name, args, ctx, SpecAxis::Vertical) } fn spacing( @@ -28,7 +28,7 @@ fn spacing( let spacing = args.expect::("spacing", name, &mut f); let commands = if let Some(spacing) = spacing { - let axis = axis.to_generic(ctx.axes); + let axis = axis.to_gen(ctx.sys); let spacing = spacing.raw_scaled(ctx.style.text.font_size()); vec![AddSpacing(spacing, SpacingKind::Hard, axis)] } else { diff --git a/src/prelude.rs b/src/prelude.rs index 8f3584303..0fab6fbbd 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,10 +1,10 @@ //! A prelude for building custom functions. -pub use super::*; -pub use crate::eval::*; -pub use crate::layout::prelude::*; -pub use crate::layout::Command::{self, *}; -pub use crate::layout::Commands; +pub use crate::eval::{Dict, DictValue, Value}; +pub use crate::layout::primitive::*; +pub use crate::layout::{layout, Command, Commands, LayoutContext}; pub use crate::style::*; -pub use crate::syntax::*; +pub use crate::syntax::{Span, Spanned, SynTree}; pub use crate::{Feedback, Pass}; + +pub use Command::*;