diff options
Diffstat (limited to 'src/layout')
| -rw-r--r-- | src/layout/actions.rs | 13 | ||||
| -rw-r--r-- | src/layout/flex.rs | 14 | ||||
| -rw-r--r-- | src/layout/mod.rs | 270 | ||||
| -rw-r--r-- | src/layout/stack.rs | 59 | ||||
| -rw-r--r-- | src/layout/text.rs | 1 | ||||
| -rw-r--r-- | src/layout/tree.rs | 44 |
6 files changed, 174 insertions, 227 deletions
diff --git a/src/layout/actions.rs b/src/layout/actions.rs index c668cf75..2528fc85 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<W: Write>(&self, f: &mut W) -> io::Result<()> { +impl Serialize for LayoutAction { + fn serialize<W: Write>(&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 53f6dfdf..96b2aa85 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 cd4986d9..31064d40 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<Layout>; + /// 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<Size>, + /// How to align this layout in a parent container. + pub alignment: LayoutAlignment, /// The actions composing this layout. pub actions: Vec<LayoutAction>, - /// Whether to debug-render this box. - pub debug_render: bool, -} - -impl Layout { - /// Serialize this layout into an output buffer. - pub fn serialize<W: Write>(&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<Layout>, -} - -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<W: Write>(&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<Layout>; - - 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<W: Write>(&self, f: &mut W) -> io::Result<()>; +} + +impl Serialize for Layout { + fn serialize<W: Write>(&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<W: Write>(&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 f46c3da0..fd88ce98 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 66ff75fd..3ca826ca 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 56fb120c..9a818963 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -11,7 +11,7 @@ pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiL struct TreeLayouter<'a, 'p> { 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); |
