diff --git a/src/eval/state.rs b/src/eval/state.rs index d90096140..295a106c9 100644 --- a/src/eval/state.rs +++ b/src/eval/state.rs @@ -4,7 +4,7 @@ use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, Font use super::Scope; use crate::geom::{Insets, Linear, Size}; -use crate::layout::{Dir, GenAlign, LayoutAlign, LayoutSystem, Sides}; +use crate::layout::{Dir, Gen2, GenAlign, Sides}; use crate::length::Length; use crate::paper::{Paper, PaperClass, PAPER_A4}; @@ -17,10 +17,10 @@ pub struct State { pub text: TextState, /// The page state. pub page: PageState, - /// The active layouting system. - pub sys: LayoutSystem, + /// The active layouting directions. + pub dirs: Gen2, /// The active alignments. - pub align: LayoutAlign, + pub aligns: Gen2, } impl Default for State { @@ -29,8 +29,8 @@ impl Default for State { scope: crate::library::_std(), text: TextState::default(), page: PageState::default(), - sys: LayoutSystem::new(Dir::LTR, Dir::TTB), - align: LayoutAlign::new(GenAlign::Start, GenAlign::Start), + dirs: Gen2::new(Dir::TTB, Dir::LTR), + aligns: Gen2::new(GenAlign::Start, GenAlign::Start), } } } diff --git a/src/geom.rs b/src/geom.rs index d38d6fe74..0089aa4af 100644 --- a/src/geom.rs +++ b/src/geom.rs @@ -6,7 +6,7 @@ pub use kurbo::*; use std::fmt::{self, Debug, Formatter}; use std::ops::*; -use crate::layout::primitive::{Dir, GenAlign, LayoutAlign, LayoutSystem, SpecAxis}; +use crate::layout::primitive::{Dir, Gen2, GenAlign, Side, SpecAxis}; /// Additional methods for [sizes]. /// @@ -18,26 +18,28 @@ pub trait SizeExt { /// Borrow the component for the specified axis mutably. fn get_mut(&mut self, axis: SpecAxis) -> &mut f64; - /// Returns the generalized version of a `Size` based on the layouting - /// system, that is: - /// - `x` describes the primary axis instead of the horizontal one. - /// - `y` describes the secondary axis instead of the vertical one. - fn generalized(self, sys: LayoutSystem) -> Self; + /// Returns the generalized version of a `Size` based on the current + /// directions. + /// + /// In the generalized version: + /// - `x` describes the cross axis instead of the horizontal one. + /// - `y` describes the main axis instead of the vertical one. + fn generalized(self, dirs: Gen2) -> Self; - /// Returns the specialized version of this generalized Size2D (inverse to + /// Returns the specialized version of this generalized `Size` (inverse to /// `generalized`). - fn specialized(self, sys: LayoutSystem) -> Self; + fn specialized(self, dirs: Gen2) -> Self; /// 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 according to `align` in - /// a container with this size. + /// The anchor position for an object to be aligned in a container with this + /// size and the given directions. /// /// This assumes the size to be generalized such that `width` corresponds to - /// the primary and `height` to the secondary axis. - fn anchor(self, align: LayoutAlign, sys: LayoutSystem) -> Point; + /// the cross and `height` to the main axis. + fn anchor(self, dirs: Gen2, aligns: Gen2) -> Point; } impl SizeExt for Size { @@ -55,25 +57,25 @@ impl SizeExt for Size { } } - fn generalized(self, sys: LayoutSystem) -> Self { - match sys.primary.axis() { - SpecAxis::Horizontal => self, - SpecAxis::Vertical => Self::new(self.height, self.width), + fn generalized(self, dirs: Gen2) -> Self { + match dirs.main.axis() { + SpecAxis::Horizontal => Self::new(self.height, self.width), + SpecAxis::Vertical => self, } } - fn specialized(self, sys: LayoutSystem) -> Self { + fn specialized(self, dirs: Gen2) -> Self { // Even though generalized is its own inverse, we still have this second // function, for clarity at the call-site. - self.generalized(sys) + self.generalized(dirs) } fn fits(self, other: Self) -> bool { self.width >= other.width && self.height >= other.height } - fn anchor(self, align: LayoutAlign, sys: LayoutSystem) -> Point { - fn anchor(length: f64, align: GenAlign, dir: Dir) -> f64 { + fn anchor(self, dirs: Gen2, aligns: Gen2) -> Point { + fn anchor(length: f64, dir: Dir, align: GenAlign) -> f64 { match (dir.is_positive(), align) { (true, GenAlign::Start) | (false, GenAlign::End) => 0.0, (_, GenAlign::Center) => length / 2.0, @@ -82,8 +84,8 @@ impl SizeExt for Size { } Point::new( - anchor(self.width, align.primary, sys.primary), - anchor(self.height, align.secondary, sys.secondary), + anchor(self.width, dirs.cross, aligns.cross), + anchor(self.height, dirs.main, aligns.main), ) } } @@ -92,34 +94,29 @@ impl SizeExt for Size { /// /// [rectangles]: ../../kurbo/struct.Rect.html pub trait RectExt { - /// Return the side identified by direction and alignment. - /// - /// Center alignment is treated the same as origin alignment. - fn get(&mut self, dir: Dir, align: GenAlign) -> f64; + /// Return the value for the given side. + fn get(self, side: Side) -> f64; - /// Get a mutable reference to the side identified by direction and - /// alignment. - /// - /// Center alignment is treated the same as origin alignment. - fn get_mut(&mut self, dir: Dir, align: GenAlign) -> &mut f64; + /// Borrow the value for the given side mutably. + fn get_mut(&mut self, side: Side) -> &mut f64; } impl RectExt for Rect { - fn get(&mut self, dir: Dir, align: GenAlign) -> f64 { - match if align == GenAlign::End { dir.inv() } else { dir } { - Dir::LTR => self.x0, - Dir::TTB => self.y0, - Dir::RTL => self.x1, - Dir::BTT => self.y1, + 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, dir: Dir, align: GenAlign) -> &mut f64 { - match if align == GenAlign::End { dir.inv() } else { dir } { - Dir::LTR => &mut self.x0, - Dir::TTB => &mut self.y0, - Dir::RTL => &mut self.x1, - Dir::BTT => &mut 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, } } } diff --git a/src/layout/line.rs b/src/layout/line.rs index 745a0d144..acf143c0c 100644 --- a/src/layout/line.rs +++ b/src/layout/line.rs @@ -1,9 +1,8 @@ //! Arranging boxes into lines. //! -//! Along the primary axis, the boxes are laid out next to each other as long as -//! they fit into a line. When necessary, a line break is inserted and the new -//! line is offset along the secondary axis by the height of the previous line -//! plus extra line spacing. +//! The boxes are laid out along the cross axis as long as they fit into a line. +//! When necessary, a line break is inserted and the new line is offset along +//! the main axis by the height of the previous line plus extra line spacing. //! //! Internally, the line layouter uses a stack layouter to stack the finished //! lines on top of each. @@ -23,8 +22,8 @@ pub struct LineLayouter { /// The context for line layouting. #[derive(Debug, Clone)] pub struct LineContext { - /// The initial layouting system, which can be updated through `set_sys`. - pub sys: LayoutSystem, + /// The layout directions. + pub dirs: Gen2, /// The spaces to layout into. pub spaces: Vec, /// Whether to spill over into copies of the last space or finish layouting @@ -40,7 +39,7 @@ impl LineLayouter { Self { stack: StackLayouter::new(StackContext { spaces: ctx.spaces.clone(), - sys: ctx.sys, + dirs: ctx.dirs, repeat: ctx.repeat, }), ctx, @@ -49,26 +48,24 @@ impl LineLayouter { } /// Add a layout. - pub fn add(&mut self, layout: BoxLayout, align: LayoutAlign) { - let sys = self.ctx.sys; - - if let Some(prev) = self.run.align { - if align.secondary != prev.secondary { + pub fn add(&mut self, layout: BoxLayout, aligns: Gen2) { + if let Some(prev) = self.run.aligns { + if aligns.main != prev.main { // TODO: Issue warning for non-fitting alignment in // non-repeating context. - let fitting = self.stack.is_fitting_alignment(align); + let fitting = self.stack.is_fitting_alignment(aligns); if !fitting && self.ctx.repeat { self.finish_space(true); } else { self.finish_line(); } - } else if align.primary < prev.primary { + } else if aligns.cross < prev.cross { self.finish_line(); - } else if align.primary > prev.primary { + } else if aligns.cross > prev.cross { let mut rest_run = LineRun::new(); - let usable = self.stack.usable().get(sys.primary.axis()); - rest_run.usable = Some(match align.primary { + let usable = self.stack.usable().get(self.ctx.dirs.cross.axis()); + rest_run.usable = Some(match aligns.cross { GenAlign::Start => unreachable!("start > x"), GenAlign::Center => usable - 2.0 * self.run.size.width, GenAlign::End => usable - self.run.size.width, @@ -84,10 +81,10 @@ impl LineLayouter { } if let LastSpacing::Soft(spacing, _) = self.run.last_spacing { - self.add_primary_spacing(spacing, SpacingKind::Hard); + self.add_cross_spacing(spacing, SpacingKind::Hard); } - let size = layout.size.generalized(sys); + let size = layout.size.generalized(self.ctx.dirs); if !self.usable().fits(size) { if !self.line_is_empty() { @@ -100,7 +97,7 @@ impl LineLayouter { } } - self.run.align = Some(align); + self.run.aligns = Some(aligns); self.run.layouts.push((self.run.size.width, layout)); self.run.size.width += size.width; @@ -114,19 +111,25 @@ 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.sys); + let mut usable = self.stack.usable().generalized(self.ctx.dirs); // If there was another run already, override the stack's size. - if let Some(primary) = self.run.usable { - usable.width = primary; + if let Some(cross) = self.run.usable { + usable.width = cross; } usable.width -= self.run.size.width; usable } + /// Finish the line and add spacing to the underlying stack. + pub fn add_main_spacing(&mut self, spacing: f64, kind: SpacingKind) { + self.finish_line_if_not_empty(); + self.stack.add_spacing(spacing, kind) + } + /// Add spacing to the line. - pub fn add_primary_spacing(&mut self, mut spacing: f64, kind: SpacingKind) { + pub fn add_cross_spacing(&mut self, mut spacing: f64, kind: SpacingKind) { match kind { SpacingKind::Hard => { spacing = spacing.min(self.usable().width); @@ -150,19 +153,6 @@ impl LineLayouter { } } - /// Finish the line and add spacing to the underlying stack. - pub fn add_secondary_spacing(&mut self, spacing: f64, kind: SpacingKind) { - self.finish_line_if_not_empty(); - self.stack.add_spacing(spacing, kind) - } - - /// Update the layouting system. - pub fn set_sys(&mut self, sys: LayoutSystem) { - self.finish_line_if_not_empty(); - self.ctx.sys = sys; - self.stack.set_sys(sys) - } - /// Update the layouting spaces. /// /// If `replace_empty` is true, the current space is replaced if there are @@ -181,7 +171,7 @@ impl LineLayouter { /// it will fit into this layouter's underlying stack. pub fn remaining(&self) -> Vec { let mut spaces = self.stack.remaining(); - *spaces[0].size.get_mut(self.ctx.sys.secondary.axis()) -= self.run.size.height; + *spaces[0].size.get_mut(self.ctx.dirs.main.axis()) -= self.run.size.height; spaces } @@ -206,17 +196,17 @@ impl LineLayouter { /// Finish the active line and start a new one. pub fn finish_line(&mut self) { - let mut layout = BoxLayout::new(self.run.size.specialized(self.ctx.sys)); - let align = self.run.align.unwrap_or_default(); + let mut layout = BoxLayout::new(self.run.size.specialized(self.ctx.dirs)); + let aligns = self.run.aligns.unwrap_or_default(); let layouts = std::mem::take(&mut self.run.layouts); for (offset, child) in layouts { - let x = match self.ctx.sys.primary.is_positive() { + let x = match self.ctx.dirs.cross.is_positive() { true => offset, false => { self.run.size.width - offset - - child.size.get(self.ctx.sys.primary.axis()) + - child.size.get(self.ctx.dirs.cross.axis()) } }; @@ -224,7 +214,7 @@ impl LineLayouter { layout.push_layout(pos, child); } - self.stack.add(layout, align); + self.stack.add(layout, aligns); self.run = LineRun::new(); self.stack.add_spacing(self.ctx.line_spacing, SpacingKind::LINE); @@ -249,7 +239,7 @@ struct LineRun { /// 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. - align: Option, + aligns: Option>, /// The amount of space left by another run on the same line or `None` if /// this is the only run so far. usable: Option, @@ -263,7 +253,7 @@ impl LineRun { Self { layouts: vec![], size: Size::ZERO, - align: None, + aligns: None, usable: None, last_spacing: LastSpacing::Hard, } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 2ba1b0de5..f9372e506 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -11,11 +11,10 @@ pub use primitive::*; pub use stack::*; pub use tree::*; -use crate::geom::{Insets, Point, Rect, RectExt, Size, SizeExt}; - use crate::diag::Diag; use crate::eval::{PageState, State, TextState}; use crate::font::SharedFontLoader; +use crate::geom::{Insets, Point, Rect, RectExt, Size, SizeExt}; use crate::shaping::Shaped; use crate::syntax::{Deco, Spanned, SynTree}; use crate::{Feedback, Pass}; @@ -29,7 +28,7 @@ pub async fn layout( let space = LayoutSpace { size: state.page.size, insets: state.page.insets(), - expansion: LayoutExpansion::new(true, true), + expansion: Spec2::new(true, true), }; let constraints = LayoutConstraints { @@ -134,7 +133,7 @@ pub struct LayoutSpace { pub insets: Insets, /// 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 expansion: LayoutExpansion, + pub expansion: Spec2, } impl LayoutSpace { @@ -154,7 +153,7 @@ impl LayoutSpace { Self { size: self.usable(), insets: Insets::ZERO, - expansion: LayoutExpansion::new(false, false), + expansion: Spec2::new(false, false), } } } @@ -172,8 +171,8 @@ pub enum Command { LayoutSyntaxTree(SynTree), /// Add a finished layout. - Add(BoxLayout, LayoutAlign), - /// Add spacing of the given kind along the primary or secondary axis. The + Add(BoxLayout, Gen2), + /// Add spacing of the given kind along the given axis. The /// kind defines how the spacing interacts with surrounding spacing. AddSpacing(f64, SpacingKind, GenAxis), @@ -187,11 +186,8 @@ pub enum Command { SetTextState(TextState), /// Update the page style. SetPageState(PageState), - /// Update the layouting system along which future boxes will be laid - /// out. This ends the current line. - SetSystem(LayoutSystem), /// Update the alignment for future boxes added to this layouting process. - SetAlignment(LayoutAlign), + SetAlignment(Gen2), } /// Defines how spacing interacts with surrounding spacing. diff --git a/src/layout/primitive.rs b/src/layout/primitive.rs index 64cd1dff5..9070c9727 100644 --- a/src/layout/primitive.rs +++ b/src/layout/primitive.rs @@ -2,30 +2,6 @@ use std::fmt::{self, Display, Formatter}; -/// 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; - -impl Default for LayoutSystem { - fn default() -> Self { - Self::new(Dir::LTR, Dir::TTB) - } -} - -/// Specifies where to align a layout in a parent container. -pub type LayoutAlign = Gen2; - -impl Default for LayoutAlign { - fn default() -> Self { - Self::new(GenAlign::Start, 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)] pub enum Dir { @@ -66,6 +42,23 @@ impl Dir { if self.is_positive() { 1.0 } else { -1.0 } } + /// The side of this direction the alignment identifies. + /// + /// `Center` alignment is treated the same as `Start` alignment. + pub fn side(self, align: GenAlign) -> Side { + let start = match self { + Self::LTR => Side::Left, + Self::RTL => Side::Right, + Self::TTB => Side::Top, + Self::BTT => Side::Bottom, + }; + + match align { + GenAlign::Start | GenAlign::Center => start, + GenAlign::End => start.inv(), + } + } + /// The inverse direction. pub fn inv(self) -> Self { match self { @@ -75,18 +68,6 @@ impl Dir { Self::BTT => Self::TTB, } } - - /// The side of this direction the alignment identifies. - /// - /// `Center` alignment is treated the same as `Start` alignment. - pub fn side(self, align: GenAlign) -> Side { - match if align == GenAlign::End { self.inv() } else { self } { - Self::LTR => Side::Left, - Self::RTL => Side::Right, - Self::TTB => Side::Top, - Self::BTT => Side::Bottom, - } - } } impl Display for Dir { @@ -100,27 +81,159 @@ impl Display for Dir { } } +/// Convert a type into its generic representation. +/// +/// The generic representation deals with main and cross axes while the specific +/// representation deals with horizontal and vertical axes. +/// +/// See also [`ToSpec`] for the inverse conversion. +/// +/// [`ToSpec`]: trait.ToSpec.html +pub trait ToGen { + /// The generic version of this type. + type Output; + + /// The generic version of this type based on the current directions. + fn to_gen(self, dirs: Gen2) -> Self::Output; +} + +/// Convert a type into its specific representation. +/// +/// The specific representation deals with horizontal and vertical axes while +/// the generic representation deals with main and cross axes. +/// +/// See also [`ToGen`] for the inverse conversion. +/// +/// [`ToGen`]: trait.ToGen.html +pub trait ToSpec { + /// The specific version of this type. + type Output; + + /// The specific version of this type based on the current directions. + fn to_spec(self, dirs: Gen2) -> Self::Output; +} + +/// A generic container with two components for the two generic axes. +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] +pub struct Gen2 { + /// The main component. + pub main: T, + /// The cross component. + pub cross: T, +} + +impl Gen2 { + /// Create a new instance from the two components. + pub fn new(main: T, cross: T) -> Self { + Self { main, cross } + } + + /// Return the component for the specified generic axis. + pub fn get(self, axis: GenAxis) -> T { + match axis { + GenAxis::Main => self.main, + GenAxis::Cross => self.cross, + } + } + + /// Borrow the component for the specified generic axis mutably. + pub fn get_mut(&mut self, axis: GenAxis) -> &mut T { + match axis { + GenAxis::Main => &mut self.main, + GenAxis::Cross => &mut self.cross, + } + } +} + +impl ToSpec for Gen2 { + type Output = Spec2; + + fn to_spec(self, dirs: Gen2) -> Self::Output { + 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 { + /// 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 component for the given specific axis. + pub fn get(self, axis: SpecAxis) -> T { + match axis { + SpecAxis::Horizontal => self.horizontal, + SpecAxis::Vertical => 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, + } + } +} + +impl ToGen for Spec2 { + type Output = Gen2; + + fn to_gen(self, dirs: Gen2) -> Self::Output { + 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 primary layouting direction into which text and lines flow. - Primary, - /// The secondary layouting direction into which paragraphs grow. - Secondary, + /// The axis pages and paragraphs are set along. + Main, + /// The axis words and lines are set along. + Cross, } impl GenAxis { - /// The specific version of this axis in the given layout system. - pub fn to_spec(self, sys: LayoutSystem) -> SpecAxis { - sys.get(self).axis() + /// The other axis. + pub fn other(self) -> Self { + match self { + Self::Main => Self::Cross, + Self::Cross => Self::Main, + } + } +} + +impl ToSpec for GenAxis { + type Output = SpecAxis; + + fn to_spec(self, dirs: Gen2) -> Self::Output { + 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::Primary => "primary", - Self::Secondary => "secondary", + Self::Main => "main", + Self::Cross => "cross", }) } } @@ -128,19 +241,31 @@ impl Display for GenAxis { /// The two specific layouting axes. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum SpecAxis { - /// The horizontal layouting axis. - Horizontal, /// The vertical layouting axis. Vertical, + /// The horizontal layouting axis. + Horizontal, } impl SpecAxis { - /// 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 + /// The other axis. + pub fn other(self) -> Self { + match self { + Self::Horizontal => Self::Vertical, + Self::Vertical => Self::Horizontal, + } + } +} + +impl ToGen for SpecAxis { + type Output = GenAxis; + + fn to_gen(self, dirs: Gen2) -> Self::Output { + if self == dirs.main.axis() { + GenAxis::Main } else { - GenAxis::Secondary + debug_assert_eq!(self, dirs.cross.axis()); + GenAxis::Cross } } } @@ -148,8 +273,8 @@ impl SpecAxis { impl Display for SpecAxis { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.pad(match self { - Self::Horizontal => "horizontal", Self::Vertical => "vertical", + Self::Horizontal => "horizontal", }) } } @@ -173,6 +298,12 @@ impl GenAlign { } } +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 { @@ -207,21 +338,36 @@ impl SpecAlign { } } - /// 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 + /// 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 ToGen for SpecAlign { + type Output = GenAlign; + + fn to_gen(self, dirs: Gen2) -> Self::Output { + let dirs = dirs.to_spec(dirs); + let get = |dir: Dir, at_positive_start| { + if dir.is_positive() == at_positive_start { + GenAlign::Start } else { - positive.inv() + GenAlign::End } }; match self { - 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::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, } } @@ -239,91 +385,6 @@ impl Display for SpecAlign { } } -/// 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, - } - } -} - -/// A generic container with two components for the two generic axes. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] -pub struct Gen2 { - /// The primary component. - pub primary: T, - /// The secondary component. - pub secondary: T, -} - -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 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)] -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 component for the given specific axis. - pub fn get(self, axis: SpecAxis) -> T { - match axis { - SpecAxis::Horizontal => self.horizontal, - SpecAxis::Vertical => 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, - } - } -} - /// A generic container with left, top, right and bottom components. #[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] pub struct Sides { @@ -360,8 +421,8 @@ impl Sides { pub fn get(self, side: Side) -> T { match side { Side::Left => self.left, - Side::Right => self.right, Side::Top => self.top, + Side::Right => self.right, Side::Bottom => self.bottom, } } @@ -370,9 +431,30 @@ impl Sides { pub fn get_mut(&mut self, side: Side) -> &mut T { match side { Side::Left => &mut self.left, - Side::Right => &mut self.right, Side::Top => &mut self.top, + Side::Right => &mut self.right, Side::Bottom => &mut self.bottom, } } } + +/// A side of a container. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum Side { + Left, + Top, + Right, + Bottom, +} + +impl Side { + /// The opposite side. + pub fn inv(self) -> Self { + match self { + Self::Left => Self::Right, + Self::Top => Self::Bottom, + Self::Right => Self::Left, + Self::Bottom => Self::Top, + } + } +} diff --git a/src/layout/stack.rs b/src/layout/stack.rs index de933fbf2..a98e4f52a 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -1,4 +1,4 @@ -//! Arranging boxes into a stack along the secondary axis. +//! Arranging boxes into a stack along the main axis. //! //! Individual layouts can be aligned at `Start`, `Center` or `End` along both //! axes. These alignments are with respect to the size of the finished layout @@ -34,8 +34,8 @@ pub struct StackLayouter { /// The context for stack layouting. #[derive(Debug, Clone)] pub struct StackContext { - /// The initial layouting system, which can be updated through `set_sys`. - pub sys: LayoutSystem, + /// The layouting directions. + pub dirs: Gen2, /// The spaces to layout into. pub spaces: Vec, /// Whether to spill over into copies of the last space or finish layouting @@ -55,15 +55,15 @@ impl StackLayouter { } /// Add a layout to the stack. - pub fn add(&mut self, layout: BoxLayout, align: LayoutAlign) { + pub fn add(&mut self, layout: BoxLayout, aligns: Gen2) { // If the alignment cannot be fitted in this space, finish it. // TODO: Issue warning for non-fitting alignment in non-repeating // context. - if !self.update_rulers(align) && self.ctx.repeat { + if !self.update_rulers(aligns) && self.ctx.repeat { self.finish_space(true); } - // Now, we add a possibly cached soft space. If the secondary alignment + // Now, we add a possibly cached soft space. If the main alignment // changed before, a possibly cached space would have already been // discarded. if let LastSpacing::Soft(spacing, _) = self.space.last_spacing { @@ -76,11 +76,11 @@ impl StackLayouter { } // Change the usable space and size of the space. - self.update_metrics(layout.size.generalized(self.ctx.sys)); + self.update_metrics(layout.size.generalized(self.ctx.dirs)); // Add the box to the vector and remember that spacings are allowed // again. - self.space.layouts.push((self.ctx.sys, align, layout)); + self.space.layouts.push((self.ctx.dirs, aligns, layout)); self.space.last_spacing = LastSpacing::None; } @@ -90,15 +90,15 @@ impl StackLayouter { // A hard space is simply an empty box. SpacingKind::Hard => { // Reduce the spacing such that it definitely fits. - let axis = self.ctx.sys.secondary.axis(); + let axis = self.ctx.dirs.main.axis(); spacing = spacing.min(self.space.usable.get(axis)); let size = Size::new(0.0, spacing); self.update_metrics(size); self.space.layouts.push(( - self.ctx.sys, - LayoutAlign::default(), - BoxLayout::new(size.specialized(self.ctx.sys)), + self.ctx.dirs, + Gen2::default(), + BoxLayout::new(size.specialized(self.ctx.dirs)), )); self.space.last_spacing = LastSpacing::Hard; @@ -121,37 +121,34 @@ impl StackLayouter { } fn update_metrics(&mut self, added: Size) { - let sys = self.ctx.sys; - - let mut size = self.space.size.generalized(sys); - let mut extra = self.space.extra.generalized(sys); + let mut size = self.space.size.generalized(self.ctx.dirs); + let mut extra = self.space.extra.generalized(self.ctx.dirs); size.width += (added.width - extra.width).max(0.0); size.height += (added.height - extra.height).max(0.0); - extra.width = extra.width.max(added.width); extra.height = (extra.height - added.height).max(0.0); - self.space.size = size.specialized(sys); - self.space.extra = extra.specialized(sys); - *self.space.usable.get_mut(sys.secondary.axis()) -= added.height; + self.space.size = size.specialized(self.ctx.dirs); + self.space.extra = extra.specialized(self.ctx.dirs); + *self.space.usable.get_mut(self.ctx.dirs.main.axis()) -= added.height; } /// Returns true if a space break is necessary. - fn update_rulers(&mut self, align: LayoutAlign) -> bool { - let allowed = self.is_fitting_alignment(align); + fn update_rulers(&mut self, aligns: Gen2) -> bool { + let allowed = self.is_fitting_alignment(aligns); if allowed { - let side = self.ctx.sys.secondary.side(GenAlign::Start); - *self.space.rulers.get_mut(side) = align.secondary; + let side = self.ctx.dirs.main.side(GenAlign::Start); + *self.space.rulers.get_mut(side) = aligns.main; } allowed } /// 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(&self, align: LayoutAlign) -> bool { - self.is_fitting_axis(self.ctx.sys.primary, align.primary) - && self.is_fitting_axis(self.ctx.sys.secondary, align.secondary) + pub(crate) fn is_fitting_alignment(&self, aligns: Gen2) -> bool { + self.is_fitting_axis(self.ctx.dirs.main, aligns.main) + && self.is_fitting_axis(self.ctx.dirs.cross, aligns.cross) } fn is_fitting_axis(&self, dir: Dir, align: GenAlign) -> bool { @@ -159,16 +156,6 @@ impl StackLayouter { && align <= self.space.rulers.get(dir.side(GenAlign::End)).inv() } - /// Update the layouting system. - pub fn set_sys(&mut self, sys: LayoutSystem) { - // Forget the spacing because it is not relevant anymore. - if sys.secondary != self.ctx.sys.secondary { - self.space.last_spacing = LastSpacing::Hard; - } - - self.ctx.sys = sys; - } - /// Update the layouting spaces. /// /// If `replace_empty` is true, the current space is replaced if there are @@ -200,12 +187,10 @@ impl StackLayouter { /// The remaining inner spaces. If something is laid out into these spaces, /// it will fit into this stack. pub fn remaining(&self) -> Vec { - let size = self.usable(); - let mut spaces = vec![LayoutSpace { - size, + size: self.usable(), insets: Insets::ZERO, - expansion: LayoutExpansion::new(false, false), + expansion: Spec2::new(false, false), }]; for space in &self.ctx.spaces[self.next_space() ..] { @@ -219,7 +204,7 @@ impl StackLayouter { pub fn usable(&self) -> Size { self.space.usable - Size::new(0.0, self.space.last_spacing.soft_or_zero()) - .specialized(self.ctx.sys) + .specialized(self.ctx.dirs) } /// Whether the current layout space is empty. @@ -274,7 +259,7 @@ impl StackLayouter { y1: start.y + self.space.size.height, }; - for (sys, _, layout) in &self.space.layouts { + for &(dirs, _, ref 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. @@ -283,41 +268,42 @@ impl StackLayouter { // Then, we 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 - // extent along the secondary axis. - *bound.get_mut(sys.secondary, GenAlign::Start) += - sys.secondary.factor() * layout.size.get(sys.secondary.axis()); + // main-axis extent. + *bound.get_mut(dirs.main.side(GenAlign::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. - // The `x` field stores the maximal primary extent in one axis-aligned - // run, while the `y` fields stores the accumulated secondary extent. + // The `x` field stores the maximal cross-axis extent in one + // axis-aligned run, while the `y` fields stores the accumulated + // main-axis extent. let mut extent = Size::ZERO; let mut rotation = SpecAxis::Vertical; for (bound, entry) in bounds.iter_mut().zip(&self.space.layouts).rev() { - let (sys, _, layout) = entry; + let &(dirs, _, ref layout) = entry; - // 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() { + // When the axes are rotated, the maximal cross-axis size + // (`extent.x`) dictates how much main-axis extent the whole run + // had. This value is thus stored in `extent.y`. The cross-axis + // extent is reset for this new axis-aligned run. + if rotation != dirs.main.axis() { extent.height = extent.width; extent.width = 0.0; - rotation = sys.secondary.axis(); + rotation = dirs.main.axis(); } // We reduce the bounding box of this layout at its end by the - // accumulated secondary extent of all layouts we have seen so far, + // accumulated main-axis extent of all layouts we have seen so far, // which are the layouts after this one since we iterate reversed. - *bound.get_mut(sys.secondary, GenAlign::End) -= - sys.secondary.factor() * extent.height; + *bound.get_mut(dirs.main.side(GenAlign::End)) -= + dirs.main.factor() * extent.height; - // Then, we add this layout's secondary extent to the accumulator. - let size = layout.size.generalized(*sys); + // Then, we add this layout's main-axis extent to the accumulator. + let size = layout.size.generalized(dirs); extent.width = extent.width.max(size.width); extent.height += size.height; } @@ -329,14 +315,14 @@ impl StackLayouter { let mut layout = BoxLayout::new(size); let layouts = std::mem::take(&mut self.space.layouts); - for ((sys, align, child), bound) in layouts.into_iter().zip(bounds) { - let size = child.size.specialized(sys); + for ((dirs, aligns, child), bound) in layouts.into_iter().zip(bounds) { + let size = child.size.specialized(dirs); // The space in which this layout is aligned is given by the // distances between the borders of its bounding box. - let usable = bound.size().generalized(sys); - let local = usable.anchor(align, sys) - size.anchor(align, sys); - let pos = bound.origin() + local.to_size().specialized(sys).to_vec2(); + let usable = bound.size().generalized(dirs); + let local = usable.anchor(dirs, aligns) - size.anchor(dirs, aligns); + let pos = bound.origin() + local.to_size().specialized(dirs).to_vec2(); layout.push_layout(pos, child); } @@ -359,7 +345,7 @@ impl StackLayouter { } } -/// A layout space composed of subspaces which can have different systems and +/// A layout space composed of subspaces which can have different directions and /// alignments. struct Space { /// The index of this space in `ctx.spaces`. @@ -367,7 +353,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<(LayoutSystem, LayoutAlign, BoxLayout)>, + layouts: Vec<(Gen2, Gen2, BoxLayout)>, /// The specialized size of this space. size: Size, /// The specialized remaining space. diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 435b7109e..4e53cedca 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -26,7 +26,7 @@ impl<'a> TreeLayouter<'a> { fn new(ctx: &'a mut LayoutContext) -> Self { let layouter = LineLayouter::new(LineContext { spaces: ctx.constraints.spaces.clone(), - sys: ctx.state.sys, + dirs: ctx.state.dirs, repeat: ctx.constraints.repeat, line_spacing: ctx.state.text.line_spacing(), }); @@ -89,14 +89,12 @@ impl<'a> TreeLayouter<'a> { fn layout_space(&mut self) { self.layouter - .add_primary_spacing(self.ctx.state.text.word_spacing(), SpacingKind::WORD); + .add_cross_spacing(self.ctx.state.text.word_spacing(), SpacingKind::WORD); } fn layout_parbreak(&mut self) { - self.layouter.add_secondary_spacing( - self.ctx.state.text.par_spacing(), - SpacingKind::PARAGRAPH, - ); + self.layouter + .add_main_spacing(self.ctx.state.text.par_spacing(), SpacingKind::PARAGRAPH); } async fn layout_text(&mut self, text: &str) { @@ -117,14 +115,14 @@ impl<'a> TreeLayouter<'a> { let boxed = shaping::shape( text, self.ctx.state.text.font_size(), - self.ctx.state.sys.primary, + self.ctx.state.dirs.cross, &mut self.ctx.loader.borrow_mut(), &self.ctx.state.text.fallback, variant, ) .await; - self.layouter.add(boxed, self.ctx.state.align); + self.layouter.add(boxed, self.ctx.state.aligns); } async fn layout_heading(&mut self, heading: &NodeHeading) { @@ -187,10 +185,10 @@ impl<'a> TreeLayouter<'a> { match command { LayoutSyntaxTree(tree) => self.layout_tree(&tree).await, - Add(layout, align) => self.layouter.add(layout, align), + Add(layout, aligns) => self.layouter.add(layout, aligns), AddSpacing(space, kind, axis) => match axis { - GenAxis::Primary => self.layouter.add_primary_spacing(space, kind), - GenAxis::Secondary => self.layouter.add_secondary_spacing(space, kind), + GenAxis::Main => self.layouter.add_main_spacing(space, kind), + GenAxis::Cross => self.layouter.add_cross_spacing(space, kind), }, BreakLine => self.layouter.finish_line(), @@ -219,7 +217,7 @@ impl<'a> TreeLayouter<'a> { let space = LayoutSpace { size: style.size, insets: style.insets(), - expansion: LayoutExpansion::new(true, true), + expansion: Spec2::new(true, true), }; self.constraints.base = space.usable(); self.layouter.set_spaces(vec![space], true); @@ -230,12 +228,7 @@ impl<'a> TreeLayouter<'a> { )); } } - - SetAlignment(align) => self.ctx.state.align = align, - SetSystem(sys) => { - self.layouter.set_sys(sys); - self.ctx.state.sys = sys; - } + SetAlignment(aligns) => self.ctx.state.aligns = aligns, } } } diff --git a/src/library/align.rs b/src/library/align.rs index e7a8eb77d..6c9c675f8 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -35,7 +35,7 @@ pub async fn align(mut args: Args, ctx: &mut LayoutContext) -> Value { Some(tree) => vec![ SetAlignment(aligns), LayoutSyntaxTree(tree), - SetAlignment(ctx.state.align), + SetAlignment(ctx.state.aligns), ], None => vec![SetAlignment(aligns)], }) @@ -45,8 +45,8 @@ pub async fn align(mut args: Args, ctx: &mut LayoutContext) -> Value { fn dedup_aligns( ctx: &mut LayoutContext, iter: impl Iterator, Spanned)>, -) -> LayoutAlign { - let mut aligns = ctx.state.align; +) -> Gen2 { + let mut aligns = ctx.state.aligns; let mut had = Gen2::new(false, false); let mut had_center = false; @@ -54,8 +54,8 @@ fn dedup_aligns( // Check whether we know which axis this alignment belongs to. if let Some(axis) = axis { // We know the axis. - let gen_axis = axis.to_gen(ctx.state.sys); - let gen_align = align.to_gen(ctx.state.sys); + let gen_axis = axis.to_gen(ctx.state.dirs); + let gen_align = align.to_gen(ctx.state.dirs); if align.axis().map_or(false, |a| a != axis) { ctx.diag(error!( @@ -73,14 +73,13 @@ fn dedup_aligns( // positional argument. debug_assert_eq!(align, SpecAlign::Center); - if had.primary && had.secondary { + 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 = LayoutAlign::new(GenAlign::Center, GenAlign::Center); - had.primary = true; - had.secondary = true; + aligns = Gen2::new(GenAlign::Center, GenAlign::Center); + had = Gen2::new(true, true); } else { had_center = true; } @@ -88,22 +87,22 @@ fn dedup_aligns( // If we we know one alignment, we can handle the unspecified `center` // alignment. - if had_center && (had.primary || had.secondary) { - if had.primary { - aligns.secondary = GenAlign::Center; - had.secondary = true; + if had_center && (had.main || had.cross) { + if had.main { + aligns.cross = GenAlign::Center; + had.cross = true; } else { - aligns.primary = GenAlign::Center; - had.primary = true; + aligns.main = GenAlign::Center; + had.main = true; } had_center = false; } } // If center has not been flushed by now, it is the only argument and then - // we default to applying it to the primary axis. + // we default to applying it to the cross axis. if had_center { - aligns.primary = GenAlign::Center; + aligns.cross = GenAlign::Center; } aligns diff --git a/src/library/boxed.rs b/src/library/boxed.rs index caeb206e2..b88f5b7ce 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -12,7 +12,7 @@ pub async fn boxed(mut args: Args, ctx: &mut LayoutContext) -> Value { let height = args.get::<_, Linear>(ctx, "height"); args.done(ctx); - let align = ctx.state.align; + let aligns = ctx.state.aligns; let constraints = &mut ctx.constraints; constraints.base = constraints.spaces[0].size; constraints.spaces.truncate(1); @@ -35,5 +35,5 @@ pub async fn boxed(mut args: Args, ctx: &mut LayoutContext) -> Value { let layouted = layout_tree(&body, ctx).await; let layout = layouted.into_iter().next().unwrap(); - Value::Commands(vec![Add(layout, align)]) + Value::Commands(vec![Add(layout, aligns)]) } diff --git a/src/library/spacing.rs b/src/library/spacing.rs index b38f06070..7a55ff98e 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -24,7 +24,7 @@ fn spacing(mut args: Args, ctx: &mut LayoutContext, axis: SpecAxis) -> Value { Value::Commands(if let Some(spacing) = spacing { let spacing = spacing.eval(ctx.state.text.font_size()); - let axis = axis.to_gen(ctx.state.sys); + let axis = axis.to_gen(ctx.state.dirs); vec![AddSpacing(spacing, SpacingKind::Hard, axis)] } else { vec![]