From 5782b82770f6923677942c3b4e2bf4f7258e47d8 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 30 Nov 2019 18:54:46 +0100 Subject: [PATCH] =?UTF-8?q?Refactor=20layouting=20base=20=E2=99=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/func/mod.rs | 59 +-------- src/layout/actions.rs | 13 +- src/layout/flex.rs | 14 +-- src/layout/mod.rs | 270 ++++++++++++++++------------------------ src/layout/stack.rs | 59 +++++---- src/layout/text.rs | 1 - src/layout/tree.rs | 44 +++---- src/lib.rs | 32 ++--- src/library/align.rs | 8 +- src/library/boxed.rs | 2 +- src/library/page.rs | 10 +- src/library/spacing.rs | 8 +- src/library/style.rs | 8 +- src/style.rs | 7 ++ src/syntax/parsing.rs | 4 +- tests/layouts/align.typ | 10 ++ 16 files changed, 228 insertions(+), 321 deletions(-) create mode 100644 tests/layouts/align.typ diff --git a/src/func/mod.rs b/src/func/mod.rs index 9c29caf24..126fd8243 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -13,7 +13,7 @@ pub mod helpers; pub mod prelude { pub use crate::func::{Command, CommandList, Function}; pub use crate::layout::{layout_tree, Layout, MultiLayout, LayoutContext, LayoutSpace}; - pub use crate::layout::{LayoutAxes, AlignedAxis, Axis, Alignment}; + pub use crate::layout::{LayoutAxes, Axis, Alignment}; pub use crate::layout::{LayoutError, LayoutResult}; pub use crate::syntax::{SyntaxTree, FuncHeader, FuncArgs, Expression, Spanned, Span}; pub use crate::syntax::{parse, ParseContext, ParseError, ParseResult}; @@ -86,6 +86,9 @@ where T: Debug + PartialEq + 'static } } +/// A sequence of commands requested for execution by a function. +pub type CommandList<'a> = Vec>; + /// Commands requested for execution by functions. #[derive(Debug)] pub enum Command<'a> { @@ -109,60 +112,6 @@ pub enum Command<'a> { SetAxes(LayoutAxes), } -/// A sequence of commands requested for execution by a function. -#[derive(Debug)] -pub struct CommandList<'a> { - pub commands: Vec>, -} - -impl<'a> CommandList<'a> { - /// Create an empty command list. - pub fn new() -> CommandList<'a> { - CommandList { commands: vec![] } - } - - /// Create a command list with commands from a vector. - pub fn from_vec(commands: Vec>) -> CommandList<'a> { - CommandList { commands } - } - - /// Add a command to the sequence. - pub fn add(&mut self, command: Command<'a>) { - self.commands.push(command); - } - - /// Whether there are any commands in this sequence. - pub fn is_empty(&self) -> bool { - self.commands.is_empty() - } -} - -impl<'a> IntoIterator for CommandList<'a> { - type Item = Command<'a>; - type IntoIter = std::vec::IntoIter>; - - fn into_iter(self) -> Self::IntoIter { - self.commands.into_iter() - } -} - -impl<'a> IntoIterator for &'a CommandList<'a> { - type Item = &'a Command<'a>; - type IntoIter = std::slice::Iter<'a, Command<'a>>; - - fn into_iter(self) -> Self::IntoIter { - self.commands.iter() - } -} - -/// Create a list of commands. -#[macro_export] -macro_rules! commands { - ($($x:expr),*$(,)*) => ( - $crate::func::CommandList::from_vec(vec![$($x,)*]) - ); -} - /// A map from identifiers to functions. pub struct Scope { parsers: HashMap>, diff --git a/src/layout/actions.rs b/src/layout/actions.rs index c668cf753..2528fc85a 100644 --- a/src/layout/actions.rs +++ b/src/layout/actions.rs @@ -1,10 +1,8 @@ //! Drawing and cofiguration actions composing layouts. use std::fmt::{self, Display, Formatter}; -use std::io::{self, Write}; -use super::Layout; -use crate::size::{Size, Size2D}; +use super::*; use LayoutAction::*; /// A layouting action. @@ -21,9 +19,8 @@ pub enum LayoutAction { DebugBox(Size2D, Size2D), } -impl LayoutAction { - /// Serialize this layout action into an easy-to-parse string representation. - pub fn serialize(&self, f: &mut W) -> io::Result<()> { +impl Serialize for LayoutAction { + fn serialize(&self, f: &mut W) -> io::Result<()> { match self { MoveAbsolute(s) => write!(f, "m {:.4} {:.4}", s.x.to_pt(), s.y.to_pt()), SetFont(i, s) => write!(f, "f {} {}", i, s.to_pt()), @@ -121,10 +118,6 @@ impl LayoutActionList { self.origin = position; self.next_pos = Some(position); - if layout.debug_render { - self.actions.push(DebugBox(position, layout.dimensions)); - } - self.extend(layout.actions); } diff --git a/src/layout/flex.rs b/src/layout/flex.rs index 53f6dfdfb..96b2aa859 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -41,7 +41,7 @@ struct PartialLine { usable: Size, content: Vec<(Size, Layout)>, dimensions: Size2D, - space: SpaceState, + space: LastSpacing, } impl PartialLine { @@ -50,7 +50,7 @@ impl PartialLine { usable, content: vec![], dimensions: Size2D::zero(), - space: SpaceState::Forbidden, + space: LastSpacing::Forbidden, } } } @@ -237,7 +237,7 @@ impl FlexLayouter { } } - if let SpaceState::Soft(space) = self.part.space { + if let LastSpacing::Soft(space) = self.part.space { self.layout_space(space, SpaceKind::Hard); } @@ -246,15 +246,15 @@ impl FlexLayouter { self.part.dimensions.x += size.x; self.part.dimensions.y.max_eq(size.y); - self.part.space = SpaceState::Allowed; + self.part.space = LastSpacing::Allowed; Ok(()) } fn layout_space(&mut self, space: Size, kind: SpaceKind) { if kind == SpaceKind::Soft { - if self.part.space != SpaceState::Forbidden { - self.part.space = SpaceState::Soft(space); + if self.part.space != LastSpacing::Forbidden { + self.part.space = LastSpacing::Soft(space); } } else { if self.part.dimensions.x + space > self.part.usable { @@ -264,7 +264,7 @@ impl FlexLayouter { } if kind == SpaceKind::Hard { - self.part.space = SpaceState::Forbidden; + self.part.space = LastSpacing::Forbidden; } } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index cd4986d9f..31064d40d 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -9,7 +9,7 @@ use toddle::Error as FontError; use crate::func::Command; use crate::size::{Size, Size2D, SizeBox}; -use crate::style::{PageStyle, TextStyle}; +use crate::style::{LayoutStyle, TextStyle}; use crate::syntax::{FuncCall, Node, SyntaxTree}; mod actions; @@ -29,99 +29,20 @@ pub mod layouters { pub use actions::{LayoutAction, LayoutActionList}; pub use layouters::*; +/// A collection of layouts. +pub type MultiLayout = Vec; + /// A sequence of layouting actions inside a box. #[derive(Debug, Clone)] pub struct Layout { /// The size of the box. pub dimensions: Size2D, + /// The baseline of the layout (as an offset from the top-left). + pub baseline: Option, + /// How to align this layout in a parent container. + pub alignment: LayoutAlignment, /// The actions composing this layout. pub actions: Vec, - /// Whether to debug-render this box. - pub debug_render: bool, -} - -impl Layout { - /// Serialize this layout into an output buffer. - pub fn serialize(&self, f: &mut W) -> io::Result<()> { - writeln!( - f, - "{:.4} {:.4}", - self.dimensions.x.to_pt(), - self.dimensions.y.to_pt() - )?; - writeln!(f, "{}", self.actions.len())?; - for action in &self.actions { - action.serialize(f)?; - writeln!(f)?; - } - Ok(()) - } -} - -/// A collection of layouts. -#[derive(Debug, Clone)] -pub struct MultiLayout { - pub layouts: Vec, -} - -impl MultiLayout { - /// Create an empty multi-layout. - pub fn new() -> MultiLayout { - MultiLayout { layouts: vec![] } - } - - /// Extract the single sublayout. This panics if the layout does not have - /// exactly one child. - pub fn into_single(mut self) -> Layout { - if self.layouts.len() != 1 { - panic!("into_single: contains not exactly one layout"); - } - self.layouts.pop().unwrap() - } - - /// Add a sublayout. - pub fn add(&mut self, layout: Layout) { - self.layouts.push(layout); - } - - /// The count of sublayouts. - pub fn count(&self) -> usize { - self.layouts.len() - } - - /// Whether this layout contains any sublayouts. - pub fn is_empty(&self) -> bool { - self.layouts.is_empty() - } -} - -impl MultiLayout { - /// Serialize this collection of layouts into an output buffer. - pub fn serialize(&self, f: &mut W) -> io::Result<()> { - writeln!(f, "{}", self.count())?; - for layout in self { - layout.serialize(f)?; - } - Ok(()) - } -} - -impl IntoIterator for MultiLayout { - type Item = Layout; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.layouts.into_iter() - } -} - -impl<'a> IntoIterator for &'a MultiLayout { - type Item = &'a Layout; - type IntoIter = std::slice::Iter<'a, Layout>; - - fn into_iter(self) -> Self::IntoIter { - self.layouts.iter() - } } /// The general context for layouting. @@ -130,20 +51,16 @@ pub struct LayoutContext<'a, 'p> { /// The font loader to retrieve fonts from when typesetting text /// using [`layout_text`]. pub loader: &'a SharedFontLoader<'p>, + /// The style for pages and text. + pub style: &'a LayoutStyle, /// Whether this layouting process handles the top-level pages. pub top_level: bool, - /// The style to set text with. This includes sizes and font classes - /// which determine which font from the loaders selection is used. - pub text_style: &'a TextStyle, - /// The current size and margins of the top-level pages. - pub page_style: PageStyle, /// The spaces to layout in. pub spaces: LayoutSpaces, - /// The axes to flow on. + /// The initial axes along which content is laid out. pub axes: LayoutAxes, - /// Whether layouts should expand to the full dimensions of the space - /// they lie on or whether should tightly fit the content. - pub expand: bool, + /// The alignment for the two axes. + pub alignment: LayoutAlignment, } /// A possibly stack-allocated vector of layout spaces. @@ -154,26 +71,31 @@ pub type LayoutSpaces = SmallVec<[LayoutSpace; 2]>; pub struct LayoutSpace { /// The maximum size of the box to layout in. pub dimensions: Size2D, + /// 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 + /// vertical and horizontal axis. + pub expand: (bool, bool), /// Padding that should be respected on each side. pub padding: SizeBox, } impl LayoutSpace { - /// The actually usable area (dimensions minus padding). - pub fn usable(&self) -> Size2D { - self.dimensions.unpadded(self.padding) - } - /// The offset from the origin to the start of content, that is, /// `(padding.left, padding.top)`. pub fn start(&self) -> Size2D { Size2D::new(self.padding.left, self.padding.right) } + /// The actually usable area (dimensions minus padding). + pub fn usable(&self) -> Size2D { + self.dimensions.unpadded(self.padding) + } + /// A layout space without padding and dimensions reduced by the padding. pub fn usable_space(&self) -> LayoutSpace { LayoutSpace { dimensions: self.usable(), + expand: (false, false), padding: SizeBox::zero(), } } @@ -182,17 +104,21 @@ impl LayoutSpace { /// The axes along which the content is laid out. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct LayoutAxes { - pub primary: AlignedAxis, - pub secondary: AlignedAxis, + pub primary: Axis, + pub secondary: Axis, } impl LayoutAxes { + pub fn new(primary: Axis, secondary: Axis) -> 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.axis.is_horizontal() { + if self.primary.is_horizontal() { size } else { Size2D { x: size.y, y: size.x } @@ -206,58 +132,9 @@ impl LayoutAxes { // at the call site, we still have this second function. self.generalize(size) } - - /// The position of the anchor specified by the two aligned axes - /// in the given generalized space. - pub fn anchor(&self, space: Size2D) -> Size2D { - Size2D::new(self.primary.anchor(space.x), self.secondary.anchor(space.y)) - } - - /// This axes with `expand` set to the given value for both axes. - pub fn expanding(&self, expand: bool) -> LayoutAxes { - LayoutAxes { - primary: self.primary.expanding(expand), - secondary: self.secondary.expanding(expand), - } - } } -/// An axis with an alignment. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct AlignedAxis { - pub axis: Axis, - pub alignment: Alignment, - pub expand: bool, -} - -impl AlignedAxis { - /// Creates an aligned axis from its three components. - pub fn new(axis: Axis, alignment: Alignment, expand: bool) -> AlignedAxis { - AlignedAxis { axis, alignment, expand } - } - - /// The position of the anchor specified by this axis on the given line. - pub fn anchor(&self, line: Size) -> Size { - use Alignment::*; - match (self.axis.is_positive(), self.alignment) { - (true, Origin) | (false, End) => Size::zero(), - (_, Center) => line / 2, - (true, End) | (false, Origin) => line, - } - } - - /// This axis with `expand` set to the given value. - pub fn expanding(&self, expand: bool) -> AlignedAxis { - AlignedAxis { expand, ..*self } - } - - /// Whether this axis needs expansion. - pub fn needs_expansion(&self) -> bool { - self.expand || self.alignment != Alignment::Origin - } -} - -/// Where to put content. +/// Directions along which content is laid out. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Axis { LeftToRight, @@ -292,6 +169,19 @@ impl Axis { } } +/// The place to put a layout in a container. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct LayoutAlignment { + pub primary: Alignment, + pub secondary: Alignment, +} + +impl LayoutAlignment { + pub fn new(primary: Alignment, secondary: Alignment) -> LayoutAlignment { + LayoutAlignment { primary, secondary } + } +} + /// Where to align content. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Alignment { @@ -300,26 +190,74 @@ pub enum Alignment { End, } +/// 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(axis: Axis, size: Size, alignment: Alignment) -> Size { + use Alignment::*; + match (axis.is_positive(), alignment) { + (true, Origin) | (false, End) => Size::zero(), + (_, Center) => size / 2, + (true, End) | (false, Origin) => size, + } +} + +/// Whitespace between boxes with different interaction properties. #[derive(Debug, Copy, Clone, PartialEq)] -pub enum SpaceKind { - /// Soft spaces are eaten up by hard spaces before or after them. - Soft, - /// Independent do not eat up soft spaces and are not eaten up by hard spaces. - Independent, - /// Hard spaces eat up soft spaces before or after them. +pub enum SpacingKind { + /// A hard space consumes surrounding soft spaces and is always layouted. Hard, + /// A soft space consumes surrounding soft spaces with higher value. + Soft(u32), } +/// The standard spacing kind used for paragraph spacing. +const PARAGRAPH_KIND: SpacingKind = SpacingKind::Soft(1); + +/// The standard spacing kind used for normal spaces between boxes. +const SPACE_KIND: SpacingKind = SpacingKind::Soft(2); + +/// The last appeared spacing. #[derive(Debug, Copy, Clone, PartialEq)] -enum SpaceState { - Soft(Size), - Forbidden, - Allowed, +enum LastSpacing { + Hard, + Soft(Size, u32), + None, } -impl SpaceState { +impl LastSpacing { fn soft_or_zero(&self) -> Size { - if let SpaceState::Soft(space) = self { *space } else { Size::zero() } + match self { + LastSpacing::Soft(space, _) => *space, + _ => Size::zero(), + } + } +} + +/// Layout components that can be serialized. +trait Serialize { + /// Serialize the data structure into an output writable. + fn serialize(&self, f: &mut W) -> io::Result<()>; +} + +impl Serialize for Layout { + fn serialize(&self, f: &mut W) -> io::Result<()> { + writeln!(f, "{:.4} {:.4}", self.dimensions.x.to_pt(), self.dimensions.y.to_pt())?; + writeln!(f, "{}", self.actions.len())?; + for action in &self.actions { + action.serialize(f)?; + writeln!(f)?; + } + Ok(()) + } +} + +impl Serialize for MultiLayout { + fn serialize(&self, f: &mut W) -> io::Result<()> { + writeln!(f, "{}", self.len())?; + for layout in self { + layout.serialize(f)?; + } + Ok(()) } } diff --git a/src/layout/stack.rs b/src/layout/stack.rs index f46c3da05..fd88ce982 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -1,23 +1,54 @@ use smallvec::smallvec; use super::*; +/// The stack layouter arranges boxes stacked onto each other. +/// +/// The boxes are laid out in the direction of the secondary layouting axis and +/// are aligned along both axes. #[derive(Debug, Clone)] pub struct StackLayouter { + /// The context for layouter. ctx: StackContext, + /// The output layouts. layouts: MultiLayout, - + /// The full layout space. space: Space, + /// The currently active subspace. sub: Subspace, } #[derive(Debug, Clone)] struct Space { + /// The index of this space in the list of spaces. index: usize, + /// Whether to add the layout for this space even if it would be empty. hard: bool, + /// The layouting actions accumulated from the subspaces. actions: LayoutActionList, + /// The used size of this space from the top-left corner to + /// the bottomright-most point of used space (specialized). combined_dimensions: Size2D, } +#[derive(Debug, Clone)] +struct Subspace { + /// The axes along which contents in this subspace are laid out. + axes: LayoutAxes, + /// 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. + size: Size2D, + /// The so-far accumulated (offset, anchor, box) triples. + boxes: Vec<(Size, Size, Layout)>, + /// The last added spacing if the last was spacing. + last_spacing: LastSpacing, +} + impl Space { fn new(index: usize, hard: bool) -> Space { Space { @@ -29,20 +60,6 @@ impl Space { } } -#[derive(Debug, Clone)] -struct Subspace { - origin: Size2D, - anchor: Size2D, - factor: i32, - - boxes: Vec<(Size, Size, Layout)>, - - usable: Size2D, - dimensions: Size2D, - - space: SpaceState, -} - impl Subspace { fn new(origin: Size2D, usable: Size2D, axes: LayoutAxes) -> Subspace { Subspace { @@ -52,7 +69,7 @@ impl Subspace { boxes: vec![], usable: axes.generalize(usable), dimensions: Size2D::zero(), - space: SpaceState::Forbidden, + space: LastSpacing::Forbidden, } } } @@ -82,7 +99,7 @@ impl StackLayouter { } pub fn add(&mut self, layout: Layout) -> LayoutResult<()> { - if let SpaceState::Soft(space) = self.sub.space { + if let LastSpacing::Soft(space) = self.sub.space { self.add_space(space, SpaceKind::Hard); } @@ -107,7 +124,7 @@ impl StackLayouter { self.sub.boxes.push((offset, anchor, layout)); self.sub.dimensions = new_dimensions; - self.sub.space = SpaceState::Allowed; + self.sub.space = LastSpacing::Allowed; Ok(()) } @@ -121,8 +138,8 @@ impl StackLayouter { pub fn add_space(&mut self, space: Size, kind: SpaceKind) { if kind == SpaceKind::Soft { - if self.sub.space != SpaceState::Forbidden { - self.sub.space = SpaceState::Soft(space); + if self.sub.space != LastSpacing::Forbidden { + self.sub.space = LastSpacing::Soft(space); } } else { if self.sub.dimensions.y + space > self.sub.usable.y { @@ -132,7 +149,7 @@ impl StackLayouter { } if kind == SpaceKind::Hard { - self.sub.space = SpaceState::Forbidden; + self.sub.space = LastSpacing::Forbidden; } } } diff --git a/src/layout/text.rs b/src/layout/text.rs index 66ff75fd4..3ca826ca1 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -73,7 +73,6 @@ impl<'a, 'p> TextLayouter<'a, 'p> { Ok(Layout { dimensions: Size2D::new(self.width, self.ctx.style.font_size), actions: self.actions.to_vec(), - debug_render: false, }) } diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 56fb120c6..9a8189632 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -11,7 +11,7 @@ pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult { ctx: LayoutContext<'a, 'p>, flex: FlexLayouter, - style: TextStyle, + style: LayoutStyle, } impl<'a, 'p> TreeLayouter<'a, 'p> { @@ -19,12 +19,12 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> { TreeLayouter { flex: FlexLayouter::new(FlexContext { - flex_spacing: flex_spacing(&ctx.text_style), + flex_spacing: flex_spacing(&ctx.style.text), spaces: ctx.spaces.clone(), axes: ctx.axes, expand: ctx.expand, }), - style: ctx.text_style.clone(), + style: ctx.style.clone(), ctx, } } @@ -37,9 +37,9 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { Node::Space => self.layout_space(), Node::Newline => self.layout_paragraph()?, - Node::ToggleItalics => self.style.toggle_class(FontClass::Italic), - Node::ToggleBold => self.style.toggle_class(FontClass::Bold), - Node::ToggleMonospace => self.style.toggle_class(FontClass::Monospace), + Node::ToggleItalics => self.style.text.toggle_class(FontClass::Italic), + Node::ToggleBold => self.style.text.toggle_class(FontClass::Bold), + Node::ToggleMonospace => self.style.text.toggle_class(FontClass::Monospace), Node::Func(func) => self.layout_func(func)?, } @@ -51,34 +51,35 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { fn layout_text(&mut self, text: &str) -> LayoutResult<()> { let layout = layout_text(text, TextContext { loader: &self.ctx.loader, - style: &self.style, + style: &self.style.text, })?; Ok(self.flex.add(layout)) } fn layout_space(&mut self) { - self.flex.add_primary_space(word_spacing(&self.style), SpaceKind::Soft); + self.flex.add_primary_space( + word_spacing(&self.style.text), + SPACE_KIND, + ); } fn layout_paragraph(&mut self) -> LayoutResult<()> { - self.flex.add_secondary_space(paragraph_spacing(&self.style), SpaceKind::Soft) + self.flex.add_secondary_space( + paragraph_spacing(&self.style.text), + PARAGRAPH_KIND, + ) } fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> { let spaces = self.flex.remaining(); - let mut axes = self.ctx.axes.expanding(false); - axes.secondary.alignment = Alignment::Origin; - let commands = func.body.val.layout(LayoutContext { loader: self.ctx.loader, + style: &self.style, top_level: false, - text_style: &self.style, - page_style: self.ctx.page_style, spaces, - axes, - expand: false, + .. self.ctx })?; for command in commands { @@ -95,10 +96,8 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { Command::Add(layout) => self.flex.add(layout), Command::AddMultiple(layouts) => self.flex.add_multiple(layouts), - Command::AddPrimarySpace(space) - => self.flex.add_primary_space(space, SpaceKind::Hard), - Command::AddSecondarySpace(space) - => self.flex.add_secondary_space(space, SpaceKind::Hard)?, + Command::AddPrimarySpace(space) => self.flex.add_primary_space(space, SpacingKind::Hard), + Command::AddSecondarySpace(space) => self.flex.add_secondary_space(space, SpacingKind::Hard)?, Command::FinishLine => self.flex.add_break(), Command::FinishRun => { self.flex.finish_run()?; }, @@ -106,16 +105,17 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { Command::BreakParagraph => self.layout_paragraph()?, - Command::SetTextStyle(style) => self.style = style, + Command::SetTextStyle(style) => self.style.text = style, Command::SetPageStyle(style) => { if !self.ctx.top_level { lerr!("page style cannot only be altered in the top-level context"); } - self.ctx.page_style = style; + self.style.page = style; self.flex.set_spaces(smallvec![ LayoutSpace { dimensions: style.dimensions, + expand: (true, true), padding: style.margins, } ], true); diff --git a/src/lib.rs b/src/lib.rs index b1408f4a3..7b6c4012e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,10 +22,10 @@ use toddle::query::{FontLoader, FontProvider, SharedFontLoader}; use crate::func::Scope; use crate::layout::{layout_tree, MultiLayout, LayoutContext}; -use crate::layout::{LayoutAxes, AlignedAxis, Axis, Alignment}; +use crate::layout::{LayoutAxes, LayoutAlignment, Axis, Alignment}; use crate::layout::{LayoutError, LayoutResult, LayoutSpace}; use crate::syntax::{SyntaxTree, parse, ParseContext, ParseError, ParseResult}; -use crate::style::{PageStyle, TextStyle}; +use crate::style::{LayoutStyle, PageStyle, TextStyle}; #[macro_use] mod macros; @@ -44,10 +44,8 @@ pub mod syntax; pub struct Typesetter<'p> { /// The font loader shared by all typesetting processes. loader: SharedFontLoader<'p>, - /// The base text style. - text_style: TextStyle, - /// The base page style. - page_style: PageStyle, + /// The base layouting style. + style: LayoutStyle, } impl<'p> Typesetter<'p> { @@ -56,21 +54,20 @@ impl<'p> Typesetter<'p> { pub fn new() -> Typesetter<'p> { Typesetter { loader: RefCell::new(FontLoader::new()), - text_style: TextStyle::default(), - page_style: PageStyle::default(), + style: LayoutStyle::default(), } } /// Set the base page style. #[inline] pub fn set_page_style(&mut self, style: PageStyle) { - self.page_style = style; + self.style.page = style; } /// Set the base text style. #[inline] pub fn set_text_style(&mut self, style: TextStyle) { - self.text_style = style; + self.style.text = style; } /// Add a font provider to the context of this typesetter. @@ -99,17 +96,14 @@ impl<'p> Typesetter<'p> { LayoutContext { loader: &self.loader, top_level: true, - text_style: &self.text_style, - page_style: self.page_style, + style: &self.style, spaces: smallvec![LayoutSpace { - dimensions: self.page_style.dimensions, - padding: self.page_style.margins, + dimensions: self.style.page.dimensions, + expand: (true, true), + padding: self.style.page.margins, }], - axes: LayoutAxes { - primary: AlignedAxis::new(Axis::LeftToRight, Alignment::Origin, false), - secondary: AlignedAxis::new(Axis::TopToBottom, Alignment::Origin, false), - }, - expand: true, + axes: LayoutAxes::new(Axis::LeftToRight, Axis::TopToBottom), + alignment: LayoutAlignment::new(Alignment::Origin, Alignment::Origin), }, )?) } diff --git a/src/library/align.rs b/src/library/align.rs index 55063ebe3..530120e79 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -69,7 +69,7 @@ function! { layout(this, ctx) { let mut axes = ctx.axes; - let primary_horizontal = axes.primary.axis.is_horizontal(); + let primary_horizontal = axes.primary.is_horizontal(); let mut primary = false; let mut secondary = false; @@ -87,7 +87,7 @@ function! { *was_set = true; - let horizontal = axis.axis.is_horizontal(); + let horizontal = axis.is_horizontal(); let alignment = generic_alignment(spec, horizontal)?; if axis.alignment == Alignment::End && alignment == Alignment::Origin { @@ -116,13 +116,13 @@ function! { set_axis(!primary_horizontal, this.vertical)?; Ok(match &this.body { - Some(body) => commands![AddMultiple( + Some(body) => vec![AddMultiple( layout_tree(body, LayoutContext { axes, .. ctx.clone() })? )], - None => commands![Command::SetAxes(axes)] + None => vec![Command::SetAxes(axes)] }) } } diff --git a/src/library/boxed.rs b/src/library/boxed.rs index 1cc4e0200..8984417cc 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -32,6 +32,6 @@ function! { ctx.spaces[0].dimensions.y = height; } - Ok(commands![AddMultiple(layout_tree(&this.body, ctx)?)]) + Ok(vec![AddMultiple(layout_tree(&this.body, ctx)?)]) } } diff --git a/src/library/page.rs b/src/library/page.rs index 40872d630..e8a808701 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -7,7 +7,7 @@ pub struct PageBreak; function! { data: PageBreak, parse: plain, - layout(_, _) { Ok(commands![FinishSpace]) } + layout(_, _) { Ok(vec![FinishSpace]) } } /// `page.size`: Set the size of pages. @@ -29,12 +29,12 @@ function! { } layout(this, ctx) { - let mut style = ctx.page_style; + let mut style = ctx.style.page; if let Some(width) = this.width { style.dimensions.x = width; } if let Some(height) = this.height { style.dimensions.y = height; } - Ok(commands![SetPageStyle(style)]) + Ok(vec![SetPageStyle(style)]) } } @@ -67,13 +67,13 @@ function! { } layout(this, ctx) { - let mut style = ctx.page_style; + let mut style = ctx.style.page; if let Some(left) = this.left { style.margins.left = left; } if let Some(top) = this.top { style.margins.top = top; } if let Some(right) = this.right { style.margins.right = right; } if let Some(bottom) = this.bottom { style.margins.bottom = bottom; } - Ok(commands![SetPageStyle(style)]) + Ok(vec![SetPageStyle(style)]) } } diff --git a/src/library/spacing.rs b/src/library/spacing.rs index d52153b1a..869a62273 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -7,7 +7,7 @@ pub struct LineBreak; function! { data: LineBreak, parse: plain, - layout(_, _) { Ok(commands![FinishLine]) } + layout(_, _) { Ok(vec![FinishLine]) } } /// `paragraph.break`: Ends the current paragraph. @@ -19,7 +19,7 @@ pub struct ParagraphBreak; function! { data: ParagraphBreak, parse: plain, - layout(_, _) { Ok(commands![BreakParagraph]) } + layout(_, _) { Ok(vec![BreakParagraph]) } } macro_rules! space_func { @@ -47,10 +47,10 @@ macro_rules! space_func { layout(this, ctx) { let $var = match this.0 { Spacing::Absolute(s) => s, - Spacing::Relative(f) => f * ctx.text_style.font_size, + Spacing::Relative(f) => f * ctx.style.text.font_size, }; - Ok(commands![$command]) + Ok(vec![$command]) } } ); diff --git a/src/library/style.rs b/src/library/style.rs index a63166cff..b2de19bba 100644 --- a/src/library/style.rs +++ b/src/library/style.rs @@ -18,16 +18,16 @@ macro_rules! stylefunc { } layout(this, ctx) { - let mut style = ctx.text_style.clone(); + let mut style = ctx.style.text.clone(); style.toggle_class(FontClass::$ident); Ok(match &this.body { - Some(body) => commands![ + Some(body) => vec![ SetTextStyle(style), LayoutTree(body), - SetTextStyle(ctx.text_style.clone()), + SetTextStyle(ctx.style.text.clone()), ], - None => commands![SetTextStyle(style)] + None => vec![SetTextStyle(style)] }) } } diff --git a/src/style.rs b/src/style.rs index ae396852c..3323e5763 100644 --- a/src/style.rs +++ b/src/style.rs @@ -4,6 +4,13 @@ use toddle::query::FontClass; use FontClass::*; use crate::size::{Size, Size2D, SizeBox}; +/// Defines properties of pages and text. +#[derive(Debug, Default, Clone)] +pub struct LayoutStyle { + pub page: PageStyle, + pub text: TextStyle, +} + /// Defines which fonts to use and how to space text. #[derive(Debug, Clone)] pub struct TextStyle { diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index b3fda7344..77697161e 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -472,7 +472,7 @@ mod tests { data: TreeFn, parse(_args, body, ctx) { Ok(TreeFn(parse!(required: body, ctx))) } - layout(_, _) { Ok(commands![]) } + layout(_, _) { Ok(vec![]) } } impl PartialEq for TreeFn { @@ -490,7 +490,7 @@ mod tests { data: BodylessFn, parse(_args, body, _ctx) { parse!(forbidden: body); Ok(BodylessFn) } - layout(_, _) { Ok(commands![]) } + layout(_, _) { Ok(vec![]) } } impl PartialEq for BodylessFn { diff --git a/tests/layouts/align.typ b/tests/layouts/align.typ new file mode 100644 index 000000000..c020ef4ba --- /dev/null +++ b/tests/layouts/align.typ @@ -0,0 +1,10 @@ +[box][ + A short sentence. [align: right][words.] + + A short sentence. [n] [align: right][words.] + + A short sentence. [paragraph.break] [align: right][words.] + + [align: bottom] + A longer sentence with a few more words. +]