From 92586d3e6895f0895fb2c88abcdd5e7160482a5b Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 10 Dec 2019 11:37:12 +0100 Subject: [PATCH] =?UTF-8?q?Progressing=20stack=20layouter=20=F0=9F=9A=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/func/mod.rs | 2 +- src/layout/actions.rs | 4 +- src/layout/flex.rs | 4 +- src/layout/mod.rs | 67 +++++---- src/layout/stack.rs | 327 ++++++++++++----------------------------- src/layout/text.rs | 2 +- src/layout/tree.rs | 2 +- src/lib.rs | 8 +- src/library/boxed.rs | 6 +- src/library/keys.rs | 2 +- src/library/maps.rs | 4 +- src/size.rs | 94 ++++++++++-- tests/layouts/test.typ | 44 +++--- tests/render.py | 8 +- 14 files changed, 250 insertions(+), 324 deletions(-) diff --git a/src/func/mod.rs b/src/func/mod.rs index 53cfece0c..c8cf23c65 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -14,7 +14,7 @@ pub mod prelude { pub use crate::func::{Scope, ParseFunc, LayoutFunc, Command, Commands}; pub use crate::layout::{ layout_tree, Layout, MultiLayout, - LayoutContext, LayoutSpace, LayoutSpaces, + LayoutContext, LayoutSpace, LayoutSpaces, LayoutExpansion, LayoutAxes, Axis, GenericAxisKind, SpecificAxisKind, LayoutAlignment, Alignment, SpacingKind, LayoutResult, diff --git a/src/layout/actions.rs b/src/layout/actions.rs index 01abc0baa..b0d2c21d8 100644 --- a/src/layout/actions.rs +++ b/src/layout/actions.rs @@ -68,8 +68,8 @@ impl LayoutActions { pub fn new() -> LayoutActions { LayoutActions { actions: vec![], - origin: Size2D::zero(), - active_font: (std::usize::MAX, Size::zero()), + origin: Size2D::ZERO, + active_font: (std::usize::MAX, Size::ZERO), next_pos: None, next_font: None, } diff --git a/src/layout/flex.rs b/src/layout/flex.rs index d984860ad..fc1a09c08 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -33,7 +33,7 @@ impl FlexLine { FlexLine { usable, actions: LayoutActions::new(), - combined_dimensions: Size2D::zero(), + combined_dimensions: Size2D::ZERO, } } } @@ -51,7 +51,7 @@ impl PartialLine { PartialLine { usable, content: vec![], - dimensions: Size2D::zero(), + dimensions: Size2D::ZERO, space: LastSpacing::Hard, } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index c34d881e2..d6bb0d6d4 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,7 +1,6 @@ //! The core layouting engine. use std::io::{self, Write}; - use smallvec::SmallVec; use toddle::query::{FontClass, SharedFontLoader}; @@ -91,7 +90,7 @@ pub struct LayoutSpace { /// Whether to expand the dimensions of the resulting layout to the full /// dimensions of this space or to shrink them to fit the content for the /// horizontal and vertical axis. - pub expand: (bool, bool), + pub expand: LayoutExpansion, } impl LayoutSpace { @@ -110,12 +109,25 @@ impl LayoutSpace { pub fn usable_space(&self) -> LayoutSpace { LayoutSpace { dimensions: self.usable(), - padding: SizeBox::zero(), - expand: (false, false), + padding: SizeBox::ZERO, + expand: LayoutExpansion::new(false, false), } } } +/// Whether to fit to content or expand to the space's size. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct LayoutExpansion { + pub horizontal: bool, + pub vertical: bool, +} + +impl LayoutExpansion { + pub fn new(horizontal: bool, vertical: bool) -> LayoutExpansion { + LayoutExpansion { horizontal, vertical } + } +} + /// The axes along which the content is laid out. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct LayoutAxes { @@ -132,28 +144,8 @@ impl LayoutAxes { LayoutAxes { primary, secondary } } - /// Returns the generalized version of a `Size2D` dependent on - /// the layouting axes, that is: - /// - The x coordinate describes the primary axis instead of the horizontal one. - /// - The y coordinate describes the secondary axis instead of the vertical one. - pub fn generalize(&self, size: Size2D) -> Size2D { - if self.primary.is_horizontal() { - size - } else { - Size2D { x: size.y, y: size.x } - } - } - - /// Returns the specialized version of this generalized Size2D. - /// (Inverse to `generalized`). - pub fn specialize(&self, size: Size2D) -> Size2D { - // In fact, generalized is its own inverse. For reasons of clarity - // at the call site, we still have this second function. - self.generalize(size) - } - /// Return the specified generic axis. - pub fn get_generic(&self, axis: GenericAxisKind) -> Axis { + pub fn generic(&self, axis: GenericAxisKind) -> Axis { match axis { GenericAxisKind::Primary => self.primary, GenericAxisKind::Secondary => self.secondary, @@ -161,8 +153,8 @@ impl LayoutAxes { } /// Return the specified specific axis. - pub fn get_specific(&self, axis: SpecificAxisKind) -> Axis { - self.get_generic(axis.generic(*self)) + pub fn specific(&self, axis: SpecificAxisKind) -> Axis { + self.generic(axis.generic(*self)) } /// Returns the generic axis kind which is the horizontal axis. @@ -222,6 +214,15 @@ impl LayoutAxes { } } +impl Default for LayoutAxes { + fn default() -> LayoutAxes { + LayoutAxes { + primary: Axis::LeftToRight, + secondary: Axis::TopToBottom, + } + } +} + /// Directions along which content is laid out. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Axis { @@ -318,7 +319,7 @@ impl SpecificAxisKind { } /// The place to put a layout in a container. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct LayoutAlignment { pub primary: Alignment, pub secondary: Alignment, @@ -331,7 +332,7 @@ impl LayoutAlignment { } /// Where to align content. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum Alignment { Origin, Center, @@ -349,6 +350,12 @@ impl Alignment { } } +impl Default for Alignment { + fn default() -> Alignment { + Alignment::Origin + } +} + /// Whitespace between boxes with different interaction properties. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum SpacingKind { @@ -377,7 +384,7 @@ impl LastSpacing { fn soft_or_zero(&self) -> Size { match self { LastSpacing::Soft(space, _) => *space, - _ => Size::zero(), + _ => Size::ZERO, } } } diff --git a/src/layout/stack.rs b/src/layout/stack.rs index b11aee790..2e4f2d3ae 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -1,4 +1,5 @@ use smallvec::smallvec; +use crate::size::{min, max}; use super::*; /// The stack layouter arranges boxes stacked onto each other. @@ -13,9 +14,6 @@ pub struct StackLayouter { layouts: MultiLayout, /// The currently active layout space. space: Space, - /// The remaining subspace of the active space. Whenever the layouting axes - /// change a new subspace is started. - sub: Subspace, } /// The context for stack layouting. @@ -37,40 +35,20 @@ struct Space { /// Whether to add the layout for this space even if it would be empty. hard: bool, /// The so-far accumulated subspaces. - subs: Vec, -} - -/// A part of a space with fixed axes and secondary alignment. -#[derive(Debug, Clone)] -struct Subspace { - /// The axes along which contents in this subspace are laid out. - axes: LayoutAxes, - /// The secondary alignment of this subspace. - alignment: Alignment, - /// The beginning of this subspace in the parent space (specialized). - origin: Size2D, - /// The total usable space of this subspace (generalized). - usable: Size2D, - /// The used size of this subspace (generalized), with - /// - `x` being the maximum of the primary size of all boxes. - /// - `y` being the total extent of all boxes and space in the secondary - /// direction. + layouts: Vec<(LayoutAxes, Layout)>, + /// The specialized size of this subspace. size: Size2D, - /// The so-far accumulated layouts. - layouts: Vec, + /// The specialized remaining space. + usable: Size2D, + /// The specialized extra-needed dimensions to affect the size at all. + extra: Size2D, + /// The maximal secondary alignment for both specialized axes (horizontal, + /// vertical). + alignment: (Alignment, Alignment), /// The last added spacing if the last added thing was spacing. last_spacing: LastSpacing, } -/// A single layout in a subspace. -#[derive(Debug, Clone)] -struct LayoutEntry { - /// The offset of this box on the secondary axis. - offset: Size, - /// The layout itself. - layout: Layout, -} - impl StackLayouter { /// Create a new stack layouter. pub fn new(ctx: StackContext) -> StackLayouter { @@ -80,55 +58,55 @@ impl StackLayouter { StackLayouter { ctx, layouts: MultiLayout::new(), - space: Space::new(0, true), - sub: Subspace::new(axes, Alignment::Origin, space.start(), space.usable()), + space: Space::new(0, true, space.usable()), } } /// Add a layout to the stack. pub fn add(&mut self, layout: Layout) -> LayoutResult<()> { - if layout.alignment.secondary != self.sub.alignment { - self.finish_subspace(layout.alignment.secondary); + // If the layout's secondary alignment is less than what we have already + // seen, it needs to go into the next space. + if layout.alignment.secondary < *self.secondary_alignment() { + self.finish_space(true); } + // We want the new maximal alignment and since the layout's secondary + // alignment is at least the previous maximum, we just take it. + *self.secondary_alignment() = layout.alignment.secondary; + // Add a cached soft space if there is one. - if let LastSpacing::Soft(space, _) = self.sub.last_spacing { - self.add_spacing(space, SpacingKind::Hard); + if let LastSpacing::Soft(spacing, _) = self.space.last_spacing { + self.add_spacing(spacing, SpacingKind::Hard); } - // The new primary size is the maximum of the current one and the - // layout's one while the secondary size grows by the layout's size. - let size = self.ctx.axes.generalize(layout.dimensions); - let mut new_size = Size2D { - x: crate::size::max(self.sub.size.x, size.x), - y: self.sub.size.y + size.y - }; - - // Find the first (sub-)space that fits the layout. - while !self.sub.usable.fits(new_size) { + // Find the first space that fits the layout. + while !self.space.usable.fits(layout.dimensions) { if self.space_is_last() && self.space_is_empty() { - error!("box of size {} does not fit into remaining stack of size {}", - size, self.sub.usable - Size2D::with_y(self.sub.size.y)); + error!("box of size {} does not fit into remaining usable size {}", + layout.dimensions, self.space.usable); } self.finish_space(true); - new_size = size; } - // The secondary offset from the start of layouts is given by the - // current primary size of the subspace. - let offset = self.sub.size.y; - self.sub.layouts.push(LayoutEntry { - offset, - layout, - }); + let axes = self.ctx.axes; + let dimensions = layout.dimensions.generalized(axes); - // The new size of the subspace is the previously calculated - // combination. - self.sub.size = new_size; + let mut size = self.space.size.generalized(axes); + let mut extra = self.space.extra.generalized(axes); - // Since the last item was a box, last spacing is reset to `None`. - self.sub.last_spacing = LastSpacing::None; + size.x += max(dimensions.x - extra.x, Size::ZERO); + size.y += max(dimensions.y - extra.y, Size::ZERO); + extra.x = max(extra.x, dimensions.x); + extra.y = max(extra.y - dimensions.y, Size::ZERO); + + self.space.size = size.specialized(axes); + self.space.extra = extra.specialized(axes); + + *self.space.usable.secondary_mut(axes) -= dimensions.y; + + self.space.layouts.push((self.ctx.axes, layout)); + self.space.last_spacing = LastSpacing::None; Ok(()) } @@ -144,30 +122,34 @@ impl StackLayouter { } /// Add secondary spacing to the stack. - pub fn add_spacing(&mut self, space: Size, kind: SpacingKind) { + pub fn add_spacing(&mut self, mut spacing: Size, kind: SpacingKind) { match kind { // A hard space is directly added to the sub's size. SpacingKind::Hard => { - if self.sub.size.y + space > self.sub.usable.y { - self.sub.size.y = self.sub.usable.y; - } else { - self.sub.size.y += space; - } + // Reduce the spacing such that definitely fits. + spacing.min_eq(self.space.usable.secondary(self.ctx.axes)); - self.sub.last_spacing = LastSpacing::Hard; + self.add(Layout { + dimensions: Size2D::with_y(spacing).specialized(self.ctx.axes), + baseline: None, + alignment: LayoutAlignment::default(), + actions: vec![], + }).expect("spacing should fit"); + + self.space.last_spacing = LastSpacing::Hard; } // A hard space is cached if it is not consumed by a hard space or // previous soft space with higher level. SpacingKind::Soft(level) => { - let consumes = match self.sub.last_spacing { + let consumes = match self.space.last_spacing { LastSpacing::None => true, LastSpacing::Soft(_, prev) if level < prev => true, _ => false, }; if consumes { - self.sub.last_spacing = LastSpacing::Soft(space, level); + self.space.last_spacing = LastSpacing::Soft(spacing, level); } } } @@ -178,13 +160,7 @@ impl StackLayouter { /// This starts a new subspace (if the axes are actually different from the /// current ones). pub fn set_axes(&mut self, axes: LayoutAxes) { - if axes != self.ctx.axes { - self.finish_subspace(Alignment::Origin); - - let (origin, usable) = self.remaining_subspace(); - self.sub = Subspace::new(axes, Alignment::Origin, origin, usable); - self.ctx.axes = axes; - } + self.ctx.axes = axes; } /// Change the layouting spaces to use. @@ -206,9 +182,9 @@ impl StackLayouter { /// out into these spaces, it will fit into this stack. pub fn remaining(&self) -> LayoutSpaces { let mut spaces = smallvec![LayoutSpace { - dimensions: self.remaining_subspace().1, - padding: SizeBox::zero(), - expand: (false, false), + dimensions: self.space.usable, + padding: SizeBox::ZERO, + expand: LayoutExpansion::new(false, false), }]; for space in &self.ctx.spaces[self.next_space()..] { @@ -220,12 +196,12 @@ impl StackLayouter { /// The usable size along the primary axis. pub fn primary_usable(&self) -> Size { - self.sub.usable.x + self.space.usable.primary(self.ctx.axes) } /// Whether the current layout space (not subspace) is empty. pub fn space_is_empty(&self) -> bool { - self.subspace_is_empty() && self.space.subs.is_empty() + self.space.size == Size2D::ZERO && self.space.layouts.is_empty() } /// Whether the current layout space is the last is the followup list. @@ -243,117 +219,36 @@ impl StackLayouter { /// Finish the current space and start a new one. pub fn finish_space(&mut self, hard: bool) { - self.finish_subspace(Alignment::Origin); - - println!(); - println!("FINISHING SPACE:"); - println!(); - let space = self.ctx.spaces[self.space.index]; - let mut subs = std::mem::replace(&mut self.space.subs, vec![]); - // --------------------------------------------------------------------- - // Compute the size of the whole space. let usable = space.usable(); - let mut max = Size2D { - x: if space.expand.0 { usable.x } else { Size::zero() }, - y: if space.expand.1 { usable.y } else { Size::zero() }, - }; + if space.expand.horizontal { self.space.size.x = usable.x; } + if space.expand.vertical { self.space.size.y = usable.y; } - // The total size is determined by the maximum position + extent of one - // of the boxes. - for sub in &subs { - max.max_eq(sub.origin + sub.axes.specialize(sub.size)); - } + let dimensions = self.space.size.padded(space.padding); - let dimensions = max.padded(space.padding); - - println!("WITH DIMENSIONS: {}", dimensions); - - println!("SUBS: {:#?}", subs); - - // --------------------------------------------------------------------- - // Justify the boxes according to their alignment and give each box - // the appropriate origin and usable space. - - // use Alignment::*; - - for sub in &mut subs { - // The usable width should not exceed the total usable width - // (previous value) or the maximum width of the layout as a whole. - sub.usable.x = crate::size::min( - sub.usable.x, - sub.axes.specialize(max - sub.origin).x, - ); - - sub.usable.y = sub.size.y; - } - - // if space.expand.1 { - // let height = subs.iter().map(|sub| sub.size.y).sum(); - // let centers = subs.iter() - // .filter(|sub| sub.alignment == Alignment::Center) - // .count() - // .max(1); - - // let grow = max.y - height; - // let center_grow = grow / (centers as i32); - - // println!("center grow = {}", center_grow); - - // let mut offset = Size::zero(); - // for sub in &mut subs { - // sub.origin.y += offset; - // if sub.alignment == Center { - // sub.usable.y += center_grow; - // offset += center_grow; - // } - // } - - // if let Some(last) = subs.last_mut() { - // last.usable.y += grow - offset; - // } - // } - - // --------------------------------------------------------------------- - // Do the thing - - // Add a debug box with this boxes size. let mut actions = LayoutActions::new(); actions.add(LayoutAction::DebugBox(dimensions)); - for sub in subs { - let LayoutAxes { primary, secondary } = sub.axes; + let mut cursor = space.start(); + for (axes, layout) in std::mem::replace(&mut self.space.layouts, vec![]) { + let LayoutAxes { primary, secondary } = axes; + let size = layout.dimensions.specialized(axes); + let alignment = layout.alignment.primary; - // The factor is +1 if the axis is positive and -1 otherwise. - let factor = sub.axes.secondary.factor(); + let primary_usable = self.space.size.primary(axes) - cursor.primary(axes); - // The anchor is the position of the origin-most point of the - // layout. - let anchor = - sub.usable.y.anchor(sub.alignment, secondary.is_positive()) - - factor * sub.size.y.anchor(sub.alignment, true); + let position = Size2D { + x: cursor.primary(axes) + + primary_usable.anchor(alignment, primary.is_positive()) + - size.x.anchor(alignment, primary.is_positive()), + y: cursor.secondary(axes), + }; - for entry in sub.layouts { - let layout = entry.layout; - let alignment = layout.alignment.primary; - let size = sub.axes.generalize(layout.dimensions); - - let x = - sub.usable.x.anchor(alignment, primary.is_positive()) - - size.x.anchor(alignment, primary.is_positive()); - - let y = anchor - + factor * entry.offset - - size.y.anchor(Alignment::Origin, secondary.is_positive()); - - let pos = sub.origin + sub.axes.specialize(Size2D::new(x, y)); - actions.add_layout(pos, layout); - } + actions.add_layout(position.specialized(axes), layout); + *cursor.secondary_mut(axes) += size.y; } - // --------------------------------------------------------------------- - self.layouts.push(Layout { dimensions, baseline: None, @@ -365,14 +260,9 @@ impl StackLayouter { } /// Start a new space with the given index. - fn start_space(&mut self, space: usize, hard: bool) { - // Start the space. - self.space = Space::new(space, hard); - - // Start the subspace. - let space = self.ctx.spaces[space]; - let axes = self.ctx.axes; - self.sub = Subspace::new(axes, Alignment::Origin, space.start(), space.usable()); + fn start_space(&mut self, index: usize, hard: bool) { + let space = self.ctx.spaces[index]; + self.space = Space::new(index, hard, space.usable()); } /// The index of the next space. @@ -380,62 +270,25 @@ impl StackLayouter { (self.space.index + 1).min(self.ctx.spaces.len() - 1) } - /// Finish the current subspace. - fn finish_subspace(&mut self, new_alignment: Alignment) { - let empty = self.subspace_is_empty(); - - let axes = self.ctx.axes; - let (origin, usable) = self.remaining_subspace(); - let new_sub = Subspace::new(axes, new_alignment, origin, usable); - let sub = std::mem::replace(&mut self.sub, new_sub); - - if !empty { - self.space.subs.push(sub); + // Access the secondary alignment in the current system of axes. + fn secondary_alignment(&mut self) -> &mut Alignment { + match self.ctx.axes.primary.is_horizontal() { + true => &mut self.space.alignment.1, + false => &mut self.space.alignment.0, } } - - /// The remaining sub - fn remaining_subspace(&self) -> (Size2D, Size2D) { - let offset = self.sub.size.y + self.sub.last_spacing.soft_or_zero(); - - let new_origin = self.sub.origin + match self.ctx.axes.secondary.is_positive() { - true => self.ctx.axes.specialize(Size2D::with_y(offset)), - false => Size2D::zero(), - }; - - let new_usable = self.ctx.axes.specialize(Size2D { - x: self.sub.usable.x, - y: self.sub.usable.y - offset, - }); - - (new_origin, new_usable) - } - - /// Whether the current layout space (not subspace) is empty. - fn subspace_is_empty(&self) -> bool { - self.sub.layouts.is_empty() && self.sub.size == Size2D::zero() - } } impl Space { - fn new(index: usize, hard: bool) -> Space { + fn new(index: usize, hard: bool, usable: Size2D) -> Space { Space { index, hard, - subs: vec![], - } - } -} - -impl Subspace { - fn new(axes: LayoutAxes, alignment: Alignment, origin: Size2D, usable: Size2D) -> Subspace { - Subspace { - axes, - alignment, - origin, - usable: axes.generalize(usable), - size: Size2D::zero(), layouts: vec![], + size: Size2D::ZERO, + usable, + extra: Size2D::ZERO, + alignment: (Alignment::Origin, Alignment::Origin), last_spacing: LastSpacing::Hard, } } diff --git a/src/layout/text.rs b/src/layout/text.rs index b5ff192eb..30620c0b1 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -42,7 +42,7 @@ impl<'a, 'p> TextLayouter<'a, 'p> { actions: LayoutActions::new(), buffer: String::new(), active_font: std::usize::MAX, - width: Size::zero(), + width: Size::ZERO, classes: ctx.style.classes.clone(), } } diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 94a50eea0..d620739d0 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -116,7 +116,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { LayoutSpace { dimensions: style.dimensions, padding: style.margins, - expand: (true, true), + expand: LayoutExpansion::new(true, true), } ], true); } diff --git a/src/lib.rs b/src/lib.rs index f2ebd5724..368d0cda3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,7 @@ use toddle::Error as FontError; use crate::func::Scope; use crate::layout::{layout_tree, MultiLayout, LayoutContext}; use crate::layout::{LayoutAxes, LayoutAlignment, Axis, Alignment}; -use crate::layout::{LayoutResult, LayoutSpace}; +use crate::layout::{LayoutResult, LayoutSpace, LayoutExpansion}; use crate::syntax::{parse, SyntaxTree, ParseContext, Span, ParseResult}; use crate::style::{LayoutStyle, PageStyle, TextStyle}; @@ -98,11 +98,11 @@ impl<'p> Typesetter<'p> { style: &self.style, spaces: smallvec![LayoutSpace { dimensions: self.style.page.dimensions, - expand: (true, true), + expand: LayoutExpansion::new(true, true), padding: self.style.page.margins, }], - axes: LayoutAxes::new(Axis::LeftToRight, Axis::TopToBottom), - alignment: LayoutAlignment::new(Alignment::Origin, Alignment::Origin), + axes: LayoutAxes::default(), + alignment: LayoutAlignment::default(), }, )?) } diff --git a/src/library/boxed.rs b/src/library/boxed.rs index d3d5b591e..7c0ea0c62 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -11,7 +11,7 @@ function! { parse(args, body, ctx) { Boxed { - body: parse!(expected: body, ctx), + body: parse!(optional: body, ctx).unwrap_or(SyntaxTree::new()), map: ExtentMap::new(&mut args, false)?, } } @@ -22,8 +22,8 @@ function! { let space = &mut ctx.spaces[0]; self.map.apply_with(ctx.axes, |axis, p| { let entity = match axis { - Horizontal => { space.expand.0 = true; &mut space.dimensions.x }, - Vertical => { space.expand.1 = true; &mut space.dimensions.y }, + Horizontal => { space.expand.horizontal = true; &mut space.dimensions.x }, + Vertical => { space.expand.vertical = true; &mut space.dimensions.y }, }; *entity = p.concretize(*entity) diff --git a/src/library/keys.rs b/src/library/keys.rs index c7e34839e..969d92be8 100644 --- a/src/library/keys.rs +++ b/src/library/keys.rs @@ -118,7 +118,7 @@ impl AlignmentKey { use AlignmentKey::*; use SpecificAxisKind::*; - let positive = axes.get_specific(axis).is_positive(); + let positive = axes.specific(axis).is_positive(); match (self, axis, positive) { (Origin, Horizontal, true) | (End, Horizontal, false) => Left, (End, Horizontal, true) | (Origin, Horizontal, false) => Right, diff --git a/src/library/maps.rs b/src/library/maps.rs index c89d46cb8..077ebc5ea 100644 --- a/src/library/maps.rs +++ b/src/library/maps.rs @@ -86,8 +86,8 @@ impl ExtentMap { let key = match arg.v.key.v.0.as_str() { "width" | "w" => AxisKey::Horizontal, "height" | "h" => AxisKey::Vertical, - "primary-size" => AxisKey::Primary, - "secondary-size" => AxisKey::Secondary, + "primary-size" | "ps" => AxisKey::Primary, + "secondary-size" | "ss" => AxisKey::Secondary, _ => if enforce { error!("expected dimension") } else { diff --git a/src/size.rs b/src/size.rs index 4a81a65de..71c5458f1 100644 --- a/src/size.rs +++ b/src/size.rs @@ -6,7 +6,7 @@ use std::iter::Sum; use std::ops::*; use std::str::FromStr; -use crate::layout::Alignment; +use crate::layout::{LayoutAxes, Alignment}; /// A general space type. #[derive(Copy, Clone, PartialEq)] @@ -51,8 +51,11 @@ pub type FSize = ScaleSize; pub type PSize = ScaleSize; impl Size { + /// The zeroed size. + pub const ZERO: Size = Size { points: 0.0 }; + /// Create a zeroed size. - pub fn zero() -> Size { Size { points: 0.0 } } + pub fn zero() -> Size { Size::ZERO } /// Create a size from an amount of points. pub fn pt(points: f32) -> Size { Size { points } } @@ -83,12 +86,17 @@ impl Size { *self = max(*self, other); } + /// Set this size to the minimum of itself and the other size. + pub fn min_eq(&mut self, other: Size) { + *self = min(*self, other); + } + /// The specialized anchor position for an item with the given alignment in a /// container with a given size along the given axis. pub fn anchor(&self, alignment: Alignment, positive: bool) -> Size { use Alignment::*; match (positive, alignment) { - (true, Origin) | (false, End) => Size::zero(), + (true, Origin) | (false, End) => Size::ZERO, (_, Center) => *self / 2, (true, End) | (false, Origin) => *self, } @@ -97,6 +105,9 @@ impl Size { } impl Size2D { + /// The zeroed 2D-size. + pub const ZERO: Size2D = Size2D { x: Size::ZERO, y: Size::ZERO }; + /// Create a new 2D-size from two sizes. pub fn new(x: Size, y: Size) -> Size2D { Size2D { x, y } @@ -104,7 +115,7 @@ impl Size2D { /// Create a 2D-size with both sizes set to zero. pub fn zero() -> Size2D { - Size2D { x: Size::zero(), y: Size::zero() } + Size2D::ZERO } /// Create a 2D-size with `x` and `y` set to the same value `s`. @@ -114,12 +125,63 @@ impl Size2D { /// Create a new 2D-size with `x` set to a value and `y` zero. pub fn with_x(x: Size) -> Size2D { - Size2D { x, y: Size::zero() } + Size2D { x, y: Size::ZERO } } /// Create a new 2D-size with `y` set to a value and `x` zero. pub fn with_y(y: Size) -> Size2D { - Size2D { x: Size::zero(), y } + Size2D { x: Size::ZERO, y } + } + + /// Access the primary size of this 2D-size. + pub fn primary(&self, axes: LayoutAxes) -> Size { + match axes.primary.is_horizontal() { + true => self.x, + false => self.y, + } + } + + /// Access the secondary size of this 2D-size. + pub fn secondary(&self, axes: LayoutAxes) -> Size { + match axes.primary.is_horizontal() { + true => self.y, + false => self.x, + } + } + + /// Access the primary size of this 2D-size. + pub fn primary_mut(&mut self, axes: LayoutAxes) -> &mut Size { + match axes.primary.is_horizontal() { + true => &mut self.x, + false => &mut self.y, + } + } + + /// Access the secondary size of this 2D-size. + pub fn secondary_mut(&mut self, axes: LayoutAxes) -> &mut Size { + match axes.primary.is_horizontal() { + true => &mut self.y, + false => &mut self.x, + } + } + + /// Returns the generalized version of a `Size2D` dependent on + /// the layouting axes, that is: + /// - The x coordinate describes the primary axis instead of the horizontal one. + /// - The y coordinate describes the secondary axis instead of the vertical one. + pub fn generalized(&self, axes: LayoutAxes) -> Size2D { + match axes.primary.is_horizontal() { + true => *self, + false => Size2D { x: self.y, y: self.x }, + } + } + + /// Returns the specialized version of this generalized Size2D. + /// (Inverse to `generalized`). + pub fn specialized(&self, axes: LayoutAxes) -> Size2D { + // In fact, generalized is its own inverse. For reasons of clarity + // at the call site, we still have this second function. + self.generalized(axes) } /// Return a 2D-size padded by the paddings of the given box. @@ -150,9 +212,24 @@ impl Size2D { self.x.max_eq(other.x); self.y.max_eq(other.y); } + + /// Set this size to the minimum of itself and the other size + /// (for both dimensions). + pub fn min_eq(&mut self, other: Size2D) { + self.x.min_eq(other.x); + self.y.min_eq(other.y); + } } impl SizeBox { + /// The zeroed size box. + pub const ZERO: SizeBox = SizeBox { + left: Size::ZERO, + top: Size::ZERO, + right: Size::ZERO, + bottom: Size::ZERO, + }; + /// Create a new box from four sizes. pub fn new(left: Size, top: Size, right: Size, bottom: Size) -> SizeBox { SizeBox { @@ -165,8 +242,7 @@ impl SizeBox { /// Create a box with all values set to zero. pub fn zero() -> SizeBox { - let zero = Size::zero(); - SizeBox::new(zero, zero, zero, zero) + SizeBox::ZERO } /// Create a box with all four fields set to the same value `s`. @@ -269,7 +345,7 @@ impl Neg for Size { impl Sum for Size { fn sum(iter: I) -> Size where I: Iterator { - iter.fold(Size::zero(), Add::add) + iter.fold(Size::ZERO, Add::add) } } diff --git a/tests/layouts/test.typ b/tests/layouts/test.typ index 063a7bfe6..68f73a737 100644 --- a/tests/layouts/test.typ +++ b/tests/layouts/test.typ @@ -1,31 +1,21 @@ [page.size: w=5cm, h=5cm] [page.margins: 0cm] -// [box: w=4cm, h=3cm][1] -// // -// [direction: ttb, ltr] -// [box: w=2cm, h=1][2] -// // -// [direction: btt, rtl] -// [align: bottom, right] -// [box: w=3cm, h=1][3] -// // -// [direction: ltr, ttb] -// [align: center, center] -// [box: w=2cm, h=2cm][4] +// Test 1 +// [box][ +// [align: center] +// [box: ps=3cm, ss=1cm] +// [direction: ttb, ltr] +// [box: ps=3cm, ss=1cm] +// [box: ps=1cm, ss=1cm] +// [box: ps=2cm, ss=1cm] +// [box: ps=1cm, ss=1cm] +// ] -[align: center] - -//[direction: primary=btt, secondary=rtl] -//[align: primary=bottom, secondary=right] -//[box][Hi] - -[box][ -//[align: primary=center, secondary=bottom] -[direction: secondary=btt] -Blabla -[v: 0.5cm] -[align: vertical=end] Origin 2] -//[align: vertical=center] Center -//[align: vertical=center] Center -//[align: vertical=end] End End End +// Test 2 +[align: secondary=top] Top +[align: secondary=center] Center +[align: secondary=bottom] Bottom +[direction: ttb, ltr] +[align: primary=bottom] +[box: w=1cm, h=1cm] diff --git a/tests/render.py b/tests/render.py index 2e105eb27..93d59ea88 100644 --- a/tests/render.py +++ b/tests/render.py @@ -105,10 +105,10 @@ class BoxRenderer: img = Image.new('RGBA', self.size, (255, 255, 255, 255)) pixels = numpy.array(img) - for i in range(0, int(height)): - for j in range(0, int(width)): - if ((i // 2) % 2 == 0) == ((j // 2) % 2 == 0): - pixels[4*i:4*(i+1), 4*j:4*(j+1)] = (225, 225, 225, 255) + # for i in range(0, int(height)): + # for j in range(0, int(width)): + # if ((i // 2) % 2 == 0) == ((j // 2) % 2 == 0): + # pixels[4*i:4*(i+1), 4*j:4*(j+1)] = (225, 225, 225, 255) self.img = Image.fromarray(pixels, 'RGBA') self.draw = ImageDraw.Draw(self.img)