diff --git a/src/func/macros.rs b/src/func/macros.rs index df7952135..2da219bc8 100644 --- a/src/func/macros.rs +++ b/src/func/macros.rs @@ -117,6 +117,7 @@ macro_rules! function { } /// Parse the body of a function. +/// /// - If the function does not expect a body, use `parse!(forbidden: body)`. /// - If the function can have a body, use `parse!(optional: body, ctx)`. /// - If the function must have a body, use `parse!(expected: body, ctx)`. @@ -146,7 +147,7 @@ macro_rules! parse { } /// Early-return with a formatted typesetting error or construct an error -/// expression without returning when prefixed with `@`. +/// expression. #[macro_export] macro_rules! error { (@unexpected_argument) => (error!(@"unexpected argument")); diff --git a/src/func/mod.rs b/src/func/mod.rs index 48e700342..208e19773 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -104,7 +104,6 @@ pub enum Command<'a> { AddSpacing(Size, SpacingKind, GenericAxis), FinishLine, - FinishRun, FinishSpace, BreakParagraph, BreakPage, diff --git a/src/layout/flex.rs b/src/layout/flex.rs deleted file mode 100644 index 72878a297..000000000 --- a/src/layout/flex.rs +++ /dev/null @@ -1,198 +0,0 @@ -use super::*; - -/// The flex layouter first arranges boxes along a primary and if necessary also -/// along a secondary axis. -#[derive(Debug, Clone)] -pub struct FlexLayouter { - axes: LayoutAxes, - flex_spacing: Size, - stack: StackLayouter, - - units: Vec, - line: FlexLine, - part: PartialLine, -} - -#[derive(Debug, Clone)] -enum FlexUnit { - Boxed(Layout), - Space(Size, SpacingKind), - SetAxes(LayoutAxes), - Break, -} - -#[derive(Debug, Clone)] -struct FlexLine { - usable: Size, - actions: LayoutActions, - combined_dimensions: Size2D, -} - -impl FlexLine { - fn new(usable: Size) -> FlexLine { - FlexLine { - usable, - actions: LayoutActions::new(), - combined_dimensions: Size2D::ZERO, - } - } -} - -#[derive(Debug, Clone)] -struct PartialLine { - usable: Size, - content: Vec<(Size, Layout)>, - dimensions: Size2D, - space: LastSpacing, -} - -impl PartialLine { - fn new(usable: Size) -> PartialLine { - PartialLine { - usable, - content: vec![], - dimensions: Size2D::ZERO, - space: LastSpacing::Hard, - } - } -} - -/// The context for flex layouting. -/// -/// See [`LayoutContext`] for details about the fields. -#[derive(Debug, Clone)] -pub struct FlexContext { - pub spaces: LayoutSpaces, - pub axes: LayoutAxes, - pub alignment: LayoutAlignment, - pub flex_spacing: Size, - pub repeat: bool, - pub debug: bool, -} - -impl FlexLayouter { - /// Create a new flex layouter. - pub fn new(ctx: FlexContext) -> FlexLayouter { - let stack = StackLayouter::new(StackContext { - spaces: ctx.spaces, - axes: ctx.axes, - alignment: ctx.alignment, - repeat: ctx.repeat, - debug: ctx.debug, - }); - - let usable = stack.primary_usable(); - - FlexLayouter { - axes: ctx.axes, - flex_spacing: ctx.flex_spacing, - stack, - - units: vec![], - line: FlexLine::new(usable), - part: PartialLine::new(usable), - } - } - - pub fn add(&mut self, layout: Layout) { - self.units.push(FlexUnit::Boxed(layout)); - } - - pub fn add_multiple(&mut self, layouts: MultiLayout) { - for layout in layouts { - self.add(layout); - } - } - - pub fn add_break(&mut self) { - self.units.push(FlexUnit::Break); - } - - pub fn add_primary_space(&mut self, space: Size, kind: SpacingKind) { - self.units.push(FlexUnit::Space(space, kind)) - } - - pub fn add_secondary_space(&mut self, space: Size, kind: SpacingKind) -> LayoutResult<()> { - if !self.run_is_empty() { - self.finish_run()?; - } - Ok(self.stack.add_spacing(space, kind)) - } - - pub fn set_axes(&mut self, axes: LayoutAxes) { - self.units.push(FlexUnit::SetAxes(axes)); - } - - pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) { - if replace_empty && self.run_is_empty() && self.stack.space_is_empty() { - self.stack.set_spaces(spaces, true); - self.start_line(); - } else { - self.stack.set_spaces(spaces, false); - } - } - - pub fn remaining(&self) -> LayoutSpaces { - self.stack.remaining() - } - - pub fn run_is_empty(&self) -> bool { - !self.units.iter().any(|unit| matches!(unit, FlexUnit::Boxed(_))) - } - - pub fn run_last_is_space(&self) -> bool { - matches!(self.units.last(), Some(FlexUnit::Space(_, _))) - } - - pub fn finish(mut self) -> LayoutResult { - self.finish_space(false)?; - self.stack.finish() - } - - pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> { - if !self.run_is_empty() { - self.finish_run()?; - } - - self.stack.finish_space(hard)?; - Ok(self.start_line()) - } - - pub fn finish_run(&mut self) -> LayoutResult { - let units = std::mem::replace(&mut self.units, vec![]); - for unit in units { - match unit { - FlexUnit::Boxed(boxed) => self.layout_box(boxed)?, - FlexUnit::Space(space, kind) => self.layout_space(space, kind), - FlexUnit::SetAxes(axes) => self.layout_set_axes(axes), - FlexUnit::Break => { self.finish_line()?; }, - } - } - - self.finish_line() - } - - fn finish_line(&mut self) -> LayoutResult { - unimplemented!() - } - - fn start_line(&mut self) { - unimplemented!() - } - - fn finish_partial_line(&mut self) { - unimplemented!() - } - - fn layout_box(&mut self, _boxed: Layout) -> LayoutResult<()> { - unimplemented!() - } - - fn layout_space(&mut self, _space: Size, _kind: SpacingKind) { - unimplemented!() - } - - fn layout_set_axes(&mut self, _axes: LayoutAxes) { - unimplemented!() - } -} diff --git a/src/layout/line.rs b/src/layout/line.rs new file mode 100644 index 000000000..5aae7dcd0 --- /dev/null +++ b/src/layout/line.rs @@ -0,0 +1,283 @@ +use super::*; + +/// The line layouter arranges boxes next to each other along a primary axis +/// and arranges the resulting lines using an underlying stack layouter. +#[derive(Debug, Clone)] +pub struct LineLayouter { + /// The context for layouting. + ctx: LineContext, + /// The underlying stack layouter. + stack: StackLayouter, + /// The currently written line. + run: LineRun, +} + +/// The context for line layouting. +#[derive(Debug, Clone)] +pub struct LineContext { + /// The spaces to layout in. + pub spaces: LayoutSpaces, + /// The initial layouting axes, which can be updated by the + /// [`LineLayouter::set_axes`] method. + pub axes: LayoutAxes, + /// Which alignment to set on the resulting layout. This affects how it will + /// be positioned in a parent box. + pub alignment: LayoutAlignment, + /// Whether to have repeated spaces or to use only the first and only once. + pub repeat: bool, + /// Whether to output a command which renders a debugging box showing the + /// extent of the layout. + pub debug: bool, + /// The line spacing. + pub line_spacing: Size, +} + +/// A simple line of boxes. +#[derive(Debug, Clone)] +struct LineRun { + /// The so-far accumulated layouts in the line. + layouts: Vec<(Size, Layout)>, + /// The width (primary size) and maximal height (secondary size) of the + /// line. + size: Size2D, + /// The alignment of all layouts in the line. + alignment: Option, + /// The remaining usable space if another differently aligned line run + /// already took up some space. + usable: Option, + /// A possibly cached soft spacing or spacing state. + last_spacing: LastSpacing, +} + +impl LineLayouter { + /// Create a new line layouter. + pub fn new(ctx: LineContext) -> LineLayouter { + LineLayouter { + stack: StackLayouter::new(StackContext { + spaces: ctx.spaces.clone(), + axes: ctx.axes, + alignment: ctx.alignment, + repeat: ctx.repeat, + debug: ctx.debug, + }), + ctx, + run: LineRun::new(), + } + } + + /// Add a layout to the run. + pub fn add(&mut self, layout: Layout) -> LayoutResult<()> { + let axes = self.ctx.axes; + + if let Some(alignment) = self.run.alignment { + if layout.alignment.secondary != alignment.secondary { + if self.stack.is_fitting_alignment(layout.alignment) { + self.finish_line()?; + } else { + self.finish_space(true)?; + } + } else if layout.alignment.primary < alignment.primary { + self.finish_line()?; + + } else if layout.alignment.primary > alignment.primary { + let mut rest_run = LineRun::new(); + + let usable = self.stack.usable().get_primary(axes); + rest_run.usable = Some(match layout.alignment.primary { + Alignment::Origin => unreachable!("origin > x"), + Alignment::Center => usable - 2 * self.run.size.x, + Alignment::End => usable - self.run.size.x, + }); + + rest_run.size.y = self.run.size.y; + + self.finish_line()?; + self.stack.add_spacing(-rest_run.size.y, SpacingKind::Hard); + + self.run = rest_run; + } + } + + if let LastSpacing::Soft(spacing, _) = self.run.last_spacing { + self.add_primary_spacing(spacing, SpacingKind::Hard); + } + + let size = layout.dimensions.generalized(axes); + + while !self.usable().fits(size) { + if !self.line_is_empty() { + self.finish_line()?; + } else { + self.finish_space(true)?; + } + } + + self.run.alignment = Some(layout.alignment); + self.run.layouts.push((self.run.size.x, layout)); + + self.run.size.x += size.x; + self.run.size.y.max_eq(size.y); + self.run.last_spacing = LastSpacing::None; + + Ok(()) + } + + /// Add multiple layouts to the run. + /// + /// This function simply calls `add` repeatedly for each layout. + pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> { + for layout in layouts { + self.add(layout)?; + } + Ok(()) + } + + /// The remaining usable size in the run. + fn usable(&self) -> Size2D { + // The base is the usable space per stack layouter. + let mut usable = self.stack.usable().generalized(self.ctx.axes); + + // If this is a alignment-continuing line, we override the primary + // usable size. + if let Some(primary) = self.run.usable { + usable.x = primary; + } + + usable.x -= self.run.size.x; + usable + } + + /// Add primary spacing to the line. + pub fn add_primary_spacing(&mut self, mut spacing: Size, kind: SpacingKind) { + match kind { + // A hard space is simply an empty box. + SpacingKind::Hard => { + spacing.min_eq(self.usable().x); + self.run.size.x += spacing; + self.run.last_spacing = LastSpacing::Hard; + } + + // A soft 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.run.last_spacing { + LastSpacing::None => true, + LastSpacing::Soft(_, prev) if level < prev => true, + _ => false, + }; + + if consumes { + self.run.last_spacing = LastSpacing::Soft(spacing, level); + } + } + } + } + + /// Finish the run and add secondary spacing to the underlying stack. + pub fn add_secondary_spacing( + &mut self, + mut spacing: Size, + kind: SpacingKind + ) -> LayoutResult<()> { + self.finish_line_if_not_empty()?; + Ok(self.stack.add_spacing(spacing, kind)) + } + + /// Change the layouting axes used by this layouter. + pub fn set_axes(&mut self, axes: LayoutAxes) -> LayoutResult<()> { + self.finish_line_if_not_empty()?; + self.ctx.axes = axes; + Ok(self.stack.set_axes(axes)) + } + + /// Change the layouting spaces to use. + /// + /// If `replace_empty` is true, the current space is replaced if there are + /// no boxes laid into it yet. Otherwise, only the followup spaces are + /// replaced. + pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) { + self.stack.set_spaces(spaces, replace_empty && self.line_is_empty()); + } + + /// Change the line spacing. + pub fn set_line_spacing(&mut self, line_spacing: Size) { + self.ctx.line_spacing = line_spacing; + } + + /// The remaining unpadded, unexpanding spaces. + pub fn remaining(&self) -> LayoutSpaces { + let mut spaces = self.stack.remaining(); + *spaces[0].dimensions.get_secondary_mut(self.ctx.axes) + -= self.run.size.y; + spaces + } + + /// Whether the currently set line is empty. + pub fn line_is_empty(&self) -> bool { + self.run.size == Size2D::ZERO && self.run.layouts.is_empty() + } + + /// Finish the last line and compute the final multi-layout. + pub fn finish(mut self) -> LayoutResult { + self.finish_line_if_not_empty()?; + self.stack.finish() + } + + /// Finish the currently active space and start a new one. + pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> { + self.finish_line_if_not_empty()?; + self.stack.finish_space(hard) + } + + /// Add the current line to the stack and start a new line. + pub fn finish_line(&mut self) -> LayoutResult<()> { + let mut actions = LayoutActions::new(); + + let layouts = std::mem::replace(&mut self.run.layouts, vec![]); + for (offset, layout) in layouts { + let x = match self.ctx.axes.primary.is_positive() { + true => offset, + false => self.run.size.x + - offset + - layout.dimensions.get_primary(self.ctx.axes), + }; + + let pos = Size2D::with_x(x); + actions.add_layout(pos, layout); + } + + self.stack.add(Layout { + dimensions: self.run.size.specialized(self.ctx.axes), + alignment: self.run.alignment + .unwrap_or(LayoutAlignment::new(Origin, Origin)), + actions: actions.to_vec(), + })?; + + self.run = LineRun::new(); + + self.stack.add_spacing(self.ctx.line_spacing, LINE_KIND); + + Ok(()) + } + + /// Finish the current line if it is not empty. + fn finish_line_if_not_empty(&mut self) -> LayoutResult<()> { + if !self.line_is_empty() { + self.finish_line() + } else { + Ok(()) + } + } +} + +impl LineRun { + fn new() -> LineRun { + LineRun { + layouts: vec![], + size: Size2D::ZERO, + alignment: None, + usable: None, + last_spacing: LastSpacing::Hard, + } + } +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index f937a0544..53c3e91e3 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -9,7 +9,7 @@ use crate::style::LayoutStyle; mod actions; mod tree; -mod flex; +mod line; mod stack; mod text; @@ -30,7 +30,7 @@ pub mod prelude { /// Different kinds of layouters (fully re-exported). pub mod layouters { pub use super::tree::layout; - pub use super::flex::{FlexLayouter, FlexContext}; + pub use super::line::{LineLayouter, LineContext}; pub use super::stack::{StackLayouter, StackContext}; pub use super::text::{layout_text, TextContext}; } @@ -174,7 +174,7 @@ impl LayoutExpansion { LayoutExpansion { horizontal, vertical } } - /// Borrow the spcified component mutably. + /// Borrow the specified component mutably. pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut bool { match axis { Horizontal => &mut self.horizontal, @@ -366,6 +366,9 @@ const PARAGRAPH_KIND: SpacingKind = SpacingKind::Soft(1); /// The standard spacing kind used for line spacing. const LINE_KIND: SpacingKind = SpacingKind::Soft(2); +/// The standard spacing kind used for word spacing. +const WORD_KIND: SpacingKind = SpacingKind::Soft(1); + /// The last appeared spacing. #[derive(Debug, Copy, Clone, PartialEq)] enum LastSpacing { diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 3c659d8a3..e05626723 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -1,11 +1,9 @@ use smallvec::smallvec; +use crate::size::ValueBox; use super::*; /// The stack layouter stack boxes onto each other along the secondary layouting /// axis. -/// -/// The boxes are aligned along both axes according to their requested -/// alignment. #[derive(Debug, Clone)] pub struct StackLayouter { /// The context for layouting. @@ -42,30 +40,21 @@ struct Space { index: usize, /// Whether to add the layout for this space even if it would be empty. hard: bool, - /// The so-far accumulated subspaces. + /// The so-far accumulated layouts. layouts: Vec<(LayoutAxes, Layout)>, - /// The specialized size of this subspace. + /// The specialized size of this space. size: Size2D, /// The specialized remaining space. usable: Size2D, /// The specialized extra-needed dimensions to affect the size at all. extra: Size2D, - /// Dictates the valid alignments for new boxes in this space. - rulers: Rulers, + /// The rulers of a space dictate which alignments for new boxes are still + /// allowed and which require a new space to be started. + rulers: ValueBox, /// The last added spacing if the last added thing was spacing. last_spacing: LastSpacing, } -/// The rulers of a space dictate which alignments for new boxes are still -/// allowed and which require a new space to be started. -#[derive(Debug, Clone)] -struct Rulers { - top: Alignment, - bottom: Alignment, - left: Alignment, - right: Alignment, -} - impl StackLayouter { /// Create a new stack layouter. pub fn new(ctx: StackContext) -> StackLayouter { @@ -157,26 +146,6 @@ impl StackLayouter { } } - /// Update the rulers to account for the new layout. Returns true if a - /// space break is necessary. - fn update_rulers(&mut self, alignment: LayoutAlignment) -> bool { - let axes = self.ctx.axes; - let allowed = self.alignment_allowed(axes.primary, alignment.primary) - && self.alignment_allowed(axes.secondary, alignment.secondary); - - if allowed { - *self.space.rulers.get(axes.secondary) = alignment.secondary; - } - - allowed - } - - /// Whether the given alignment is still allowed according to the rulers. - fn alignment_allowed(&mut self, direction: Direction, alignment: Alignment) -> bool { - alignment >= *self.space.rulers.get(direction) - && alignment <= self.space.rulers.get(direction.inv()).inv() - } - /// Update the size metrics to reflect that a layout or spacing with the /// given generalized dimensions has been added. fn update_metrics(&mut self, dimensions: Size2D) { @@ -196,10 +165,31 @@ impl StackLayouter { *self.space.usable.get_secondary_mut(axes) -= dimensions.y; } + /// Update the rulers to account for the new layout. Returns true if a + /// space break is necessary. + fn update_rulers(&mut self, alignment: LayoutAlignment) -> bool { + let allowed = self.is_fitting_alignment(alignment); + if allowed { + *self.space.rulers.get_mut(self.ctx.axes.secondary, Origin) + = alignment.secondary; + } + allowed + } + + /// Whether a layout with the given alignment can still be layouted in the + /// active space. + pub fn is_fitting_alignment(&mut self, alignment: LayoutAlignment) -> bool { + self.is_fitting_axis(self.ctx.axes.primary, alignment.primary) + && self.is_fitting_axis(self.ctx.axes.secondary, alignment.secondary) + } + + /// Whether the given alignment is still allowed according to the rulers. + fn is_fitting_axis(&mut self, direction: Direction, alignment: Alignment) -> bool { + alignment >= *self.space.rulers.get_mut(direction, Origin) + && alignment <= self.space.rulers.get_mut(direction, End).inv() + } + /// Change the layouting axes used by this layouter. - /// - /// This starts a new subspace (if the axes are actually different from the - /// current ones). pub fn set_axes(&mut self, axes: LayoutAxes) { // Forget the spacing because it is not relevant anymore. if axes.secondary != self.ctx.axes.secondary { @@ -227,9 +217,7 @@ impl StackLayouter { /// The remaining unpadded, unexpanding spaces. If a multi-layout is laid /// out into these spaces, it will fit into this stack. pub fn remaining(&self) -> LayoutSpaces { - let dimensions = self.space.usable - - Size2D::with_y(self.space.last_spacing.soft_or_zero()) - .specialized(self.ctx.axes); + let dimensions = self.usable(); let mut spaces = smallvec![LayoutSpace { dimensions, @@ -244,9 +232,11 @@ impl StackLayouter { spaces } - /// The usable size along the primary axis. - pub fn primary_usable(&self) -> Size { - self.space.usable.get_primary(self.ctx.axes) + /// The remaining usable size. + pub fn usable(&self) -> Size2D { + self.space.usable + - Size2D::with_y(self.space.last_spacing.soft_or_zero()) + .specialized(self.ctx.axes) } /// Whether the current layout space (not subspace) is empty. @@ -409,24 +399,8 @@ impl Space { size: Size2D::ZERO, usable, extra: Size2D::ZERO, - rulers: Rulers { - top: Origin, - bottom: Origin, - left: Origin, - right: Origin, - }, + rulers: ValueBox::with_all(Origin), last_spacing: LastSpacing::Hard, } } } - -impl Rulers { - fn get(&mut self, direction: Direction) -> &mut Alignment { - match direction { - TopToBottom => &mut self.top, - BottomToTop => &mut self.bottom, - LeftToRight => &mut self.left, - RightToLeft => &mut self.right, - } - } -} diff --git a/src/layout/text.rs b/src/layout/text.rs index 996c51397..e97214294 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -12,6 +12,7 @@ use super::*; pub struct TextContext<'a, 'p> { pub loader: &'a SharedFontLoader<'p>, pub style: &'a TextStyle, + pub axes: LayoutAxes, pub alignment: LayoutAlignment, } @@ -50,22 +51,14 @@ impl<'a, 'p> TextLayouter<'a, 'p> { /// Layout the text fn layout(mut self) -> LayoutResult { - for c in self.text.chars() { - let (index, char_width) = self.select_font(c)?; - - self.width += char_width; - - if self.active_font != index { - if !self.buffer.is_empty() { - self.actions.add(LayoutAction::WriteText(self.buffer)); - self.buffer = String::new(); - } - - self.actions.add(LayoutAction::SetFont(index, self.ctx.style.font_size())); - self.active_font = index; + if self.ctx.axes.primary.is_positive() { + for c in self.text.chars() { + self.layout_char(c)?; + } + } else { + for c in self.text.chars().rev() { + self.layout_char(c)?; } - - self.buffer.push(c); } if !self.buffer.is_empty() { @@ -79,6 +72,27 @@ impl<'a, 'p> TextLayouter<'a, 'p> { }) } + /// Layout an individual character. + fn layout_char(&mut self, c: char) -> LayoutResult<()> { + let (index, char_width) = self.select_font(c)?; + + self.width += char_width; + + if self.active_font != index { + if !self.buffer.is_empty() { + let text = std::mem::replace(&mut self.buffer, String::new()); + self.actions.add(LayoutAction::WriteText(text)); + } + + self.actions.add(LayoutAction::SetFont(index, self.ctx.style.font_size())); + self.active_font = index; + } + + self.buffer.push(c); + + Ok(()) + } + /// Select the best font for a character and return its index along with /// the width of the char in the font. fn select_font(&mut self, c: char) -> LayoutResult<(usize, Size)> { diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 7910fdd3e..db59ca8da 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -16,7 +16,7 @@ pub fn layout(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult { ctx: LayoutContext<'a, 'p>, - stack: StackLayouter, + layouter: LineLayouter, style: LayoutStyle, } @@ -24,12 +24,13 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { /// Create a new syntax tree layouter. fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> { TreeLayouter { - stack: StackLayouter::new(StackContext { + layouter: LineLayouter::new(LineContext { spaces: ctx.spaces.clone(), axes: ctx.axes, alignment: ctx.alignment, repeat: ctx.repeat, debug: ctx.debug, + line_spacing: ctx.style.text.line_spacing(), }), style: ctx.style.clone(), ctx, @@ -59,29 +60,27 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { let layout = layout_text(text, TextContext { loader: &self.ctx.loader, style: &self.style.text, + axes: self.ctx.axes, alignment: self.ctx.alignment, })?; - self.stack.add(layout) + self.layouter.add(layout) } fn layout_space(&mut self) { - + self.layouter.add_primary_spacing(self.style.text.word_spacing(), WORD_KIND); } fn layout_paragraph(&mut self) -> LayoutResult<()> { - Ok(self.stack.add_spacing( - paragraph_spacing(&self.style.text), - PARAGRAPH_KIND, - )) + self.layouter.add_secondary_spacing(self.style.text.paragraph_spacing(), PARAGRAPH_KIND) } fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> { let commands = func.0.layout(LayoutContext { style: &self.style, - spaces: self.stack.remaining(), + spaces: self.layouter.remaining(), nested: true, - debug: true, + debug: false, .. self.ctx })?; @@ -98,26 +97,28 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { match command { LayoutTree(tree) => self.layout(tree)?, - Add(layout) => self.stack.add(layout)?, - AddMultiple(layouts) => self.stack.add_multiple(layouts)?, + Add(layout) => self.layouter.add(layout)?, + AddMultiple(layouts) => self.layouter.add_multiple(layouts)?, AddSpacing(space, kind, axis) => match axis { - Primary => {}, - Secondary => self.stack.add_spacing(space, kind), + Primary => self.layouter.add_primary_spacing(space, kind), + Secondary => self.layouter.add_secondary_spacing(space, kind)?, } - FinishLine => {}, - FinishRun => {}, - FinishSpace => self.stack.finish_space(true)?, + FinishLine => self.layouter.finish_line()?, + FinishSpace => self.layouter.finish_space(true)?, BreakParagraph => self.layout_paragraph()?, BreakPage => { if self.ctx.nested { error!("page break cannot be issued from nested context"); } - self.stack.finish_space(true)? + self.layouter.finish_space(true)? } - SetTextStyle(style) => self.style.text = style, + SetTextStyle(style) => { + self.layouter.set_line_spacing(style.line_spacing()); + self.style.text = style; + } SetPageStyle(style) => { if self.ctx.nested { error!("page style cannot be altered in nested context"); @@ -127,7 +128,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { let margins = style.margins(); self.ctx.base = style.dimensions.unpadded(margins); - self.stack.set_spaces(smallvec![ + self.layouter.set_spaces(smallvec![ LayoutSpace { dimensions: style.dimensions, padding: margins, @@ -137,7 +138,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { } SetAlignment(alignment) => self.ctx.alignment = alignment, SetAxes(axes) => { - self.stack.set_axes(axes); + self.layouter.set_axes(axes); self.ctx.axes = axes; } } @@ -146,18 +147,6 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { } fn finish(self) -> LayoutResult { - self.stack.finish() + self.layouter.finish() } } - -fn word_spacing(style: &TextStyle) -> Size { - style.word_spacing * style.font_size() -} - -fn flex_spacing(style: &TextStyle) -> Size { - (style.line_spacing - 1.0) * style.font_size() -} - -fn paragraph_spacing(style: &TextStyle) -> Size { - (style.paragraph_spacing - 1.0) * style.font_size() -} diff --git a/src/library/boxed.rs b/src/library/boxed.rs index ece8b20f9..7333c50f2 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -9,28 +9,30 @@ function! { pub struct Boxed { body: SyntaxTree, map: ExtentMap, - debug: bool, + debug: Option, } parse(args, body, ctx) { Boxed { body: parse!(optional: body, ctx).unwrap_or(SyntaxTree::new()), map: ExtentMap::new(&mut args, false)?, - debug: args.get_key_opt::("debug")?.unwrap_or(true), + debug: args.get_key_opt::("debug")?, } } layout(self, mut ctx) { ctx.repeat = false; - ctx.debug = self.debug; + + if let Some(debug) = self.debug { + ctx.debug = debug; + } let map = self.map.dedup(ctx.axes)?; - // Try to layout this box in all spaces. + // Try to layout this box in all spaces until it fits into some space. let mut error = None; - for &space in &ctx.spaces { + for &(mut space) in &ctx.spaces { let mut ctx = ctx.clone(); - let mut space = space; for &axis in &[Horizontal, Vertical] { if let Some(psize) = map.get(axis) { diff --git a/src/size.rs b/src/size.rs index 090967837..a3380f342 100644 --- a/src/size.rs +++ b/src/size.rs @@ -1,6 +1,6 @@ -//! Different-dimensional spacing types. +//! Different-dimensional value and spacing types. -use std::fmt::{self, Display, Formatter}; +use std::fmt::{self, Debug, Display, Formatter}; use std::iter::Sum; use std::ops::*; use std::str::FromStr; @@ -8,7 +8,7 @@ use std::str::FromStr; use crate::layout::prelude::*; /// A general spacing type. -#[derive(Copy, Clone, PartialEq, PartialOrd)] +#[derive(Default, Copy, Clone, PartialEq, PartialOrd)] pub struct Size { /// The size in typographic points (1/72 inches). pub points: f32, @@ -92,64 +92,111 @@ impl Sum for Size { } } -/// A position or extent in 2-dimensional space. +/// Either an absolute size or a factor of some entity. #[derive(Copy, Clone, PartialEq)] -pub struct Size2D { - /// The horizontal coordinate. - pub x: Size, - /// The vertical coordinate. - pub y: Size, +pub enum ScaleSize { + Absolute(Size), + Scaled(f32), } -impl Size2D { - /// The zeroed 2D-size. - pub const ZERO: Size2D = Size2D { x: Size::ZERO, y: Size::ZERO }; +impl ScaleSize { + /// Use the absolute value or scale the entity. + pub fn scaled(&self, entity: Size) -> Size { + match self { + ScaleSize::Absolute(s) => *s, + ScaleSize::Scaled(s) => *s * entity, + } + } +} - /// Create a new 2D-size from two sizes. - pub fn new(x: Size, y: Size) -> Size2D { Size2D { x, y } } +impl Display for ScaleSize { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + ScaleSize::Absolute(size) => write!(f, "{}", size), + ScaleSize::Scaled(scale) => write!(f, "x{}", scale), + } + } +} - /// 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 } } +debug_display!(ScaleSize); - /// 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 } } +/// A scale size that is scaled by the font size. +pub type FSize = ScaleSize; - /// Create a 2D-size with `x` and `y` set to the same value `s`. - pub fn with_all(s: Size) -> Size2D { Size2D { x: s, y: s } } +/// A scale size that is scaled by the size of the padded parent container. +pub type PSize = ScaleSize; + +/// A value in two dimensions. +#[derive(Default, Copy, Clone, PartialEq)] +pub struct Value2D { + /// The horizontal component. + pub x: T, + /// The vertical component. + pub y: T, +} + +impl Value2D { + /// Create a new 2D-value from two values. + pub fn new(x: T, y: T) -> Value2D { Value2D { x, y } } + + /// Create a new 2D-value with `x` set to a value and `y` to default. + pub fn with_x(x: T) -> Value2D where T: Default { + Value2D { x, y: T::default() } + } + + /// Create a new 2D-value with `y` set to a value and `x` to default. + pub fn with_y(y: T) -> Value2D where T: Default { + Value2D { x: T::default(), y } + } + + /// Create a new 2D-value with the primary axis set to a value and the other + /// one to default. + pub fn with_primary(v: T, axes: LayoutAxes) -> Value2D where T: Default { + Value2D::with_x(v).generalized(axes) + } + + /// Create a new 2D-value with the secondary axis set to a value and the + /// other one to default. + pub fn with_secondary(v: T, axes: LayoutAxes) -> Value2D where T: Default { + Value2D::with_y(v).generalized(axes) + } + + /// Create a 2D-value with `x` and `y` set to the same value `s`. + pub fn with_all(s: T) -> Value2D { Value2D { x: s, y: s } } /// Get the specificed component. - pub fn get(self, axis: SpecificAxis) -> Size { + pub fn get(self, axis: SpecificAxis) -> T { match axis { Horizontal => self.x, Vertical => self.y, } } - /// Get the specificed component mutably. - pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut Size { + /// Borrow the specificed component mutably. + pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut T { match axis { Horizontal => &mut self.x, Vertical => &mut self.y, } } - /// Access the primary size of this specialized 2D-size. - pub fn get_primary(self, axes: LayoutAxes) -> Size { + /// Return the primary value of this specialized 2D-value. + pub fn get_primary(self, axes: LayoutAxes) -> T { if axes.primary.axis() == Horizontal { self.x } else { self.y } } - /// Access the primary size of this specialized 2D-size mutably. - pub fn get_primary_mut(&mut self, axes: LayoutAxes) -> &mut Size { + /// Borrow the primary value of this specialized 2D-value mutably. + pub fn get_primary_mut(&mut self, axes: LayoutAxes) -> &mut T { if axes.primary.axis() == Horizontal { &mut self.x } else { &mut self.y } } - /// Access the secondary size of this specialized 2D-size. - pub fn get_secondary(self, axes: LayoutAxes) -> Size { + /// Return the secondary value of this specialized 2D-value. + pub fn get_secondary(self, axes: LayoutAxes) -> T { if axes.primary.axis() == Horizontal { self.y } else { self.x } } - /// Access the secondary size of this specialized 2D-size mutably. - pub fn get_secondary_mut(&mut self, axes: LayoutAxes) -> &mut Size { + /// Borrow the secondary value of this specialized 2D-value mutably. + pub fn get_secondary_mut(&mut self, axes: LayoutAxes) -> &mut T { if axes.primary.axis() == Horizontal { &mut self.y } else { &mut self.x } } @@ -157,20 +204,40 @@ impl Size2D { /// axes, that is: /// - `x` describes the primary axis instead of the horizontal one. /// - `y` describes the secondary axis instead of the vertical one. - pub fn generalized(self, axes: LayoutAxes) -> Size2D { + pub fn generalized(self, axes: LayoutAxes) -> Value2D { match axes.primary.axis() { Horizontal => self, - Vertical => Size2D { x: self.y, y: self.x }, + Vertical => Value2D { x: self.y, y: self.x }, } } /// Returns the specialized version of this generalized Size2D (inverse to /// `generalized`). - pub fn specialized(self, axes: LayoutAxes) -> Size2D { + pub fn specialized(self, axes: LayoutAxes) -> Value2D { // In fact, generalized is its own inverse. For reasons of clarity // at the call site, we still have this second function. self.generalized(axes) } +} + +impl Display for Value2D where T: Display { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "[{}, {}]", self.x, self.y) + } +} + +impl Debug for Value2D where T: Debug { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "[{:?}, {:?}]", self.x, self.y) + } +} + +/// A position or extent in 2-dimensional space. +pub type Size2D = Value2D; + +impl Size2D { + /// The zeroed 2D-size. + pub const ZERO: Size2D = Size2D { x: Size::ZERO, y: Size::ZERO }; /// Whether the given 2D-size fits into this one, that is, both coordinate /// values are smaller or equal. @@ -207,14 +274,6 @@ impl Size2D { } } -impl Display for Size2D { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "[{}, {}]", self.x, self.y) - } -} - -debug_display!(Size2D); - impl Neg for Size2D { type Output = Size2D; @@ -226,11 +285,41 @@ impl Neg for Size2D { } } -/// A size in four dimensions. -pub type SizeBox = ValueBox; +/// A value that is stretchable in an interval from a minimal through an optimal +/// to a maximal value. +pub struct StretchValue { + /// The minimum this value can be stretched to. + pub min: T, + /// The optimum for this value. + pub opt: T, + /// The maximum this value can be stretched to. + pub max: T, +} + +impl StretchValue { + /// Create a new stretch size from minimum, optimal and maximum values. + pub fn new(min: T, opt: T, max: T) -> StretchValue { + StretchValue { min, opt, max } + } +} + +impl Display for StretchValue where T: Display { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "({}, {}, {})", self.min, self.opt, self.max) + } +} + +impl Debug for StretchValue where T: Debug { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "({:?}, {:?}, {:?})", self.min, self.opt, self.max) + } +} + +/// A size that is stretchable. +pub type StretchSize = StretchValue; /// A value in four dimensions. -#[derive(Copy, Clone, PartialEq)] +#[derive(Default, Copy, Clone, PartialEq)] pub struct ValueBox { /// The left extent. pub left: T, @@ -242,16 +331,6 @@ pub struct ValueBox { pub bottom: T, } -impl SizeBox { - /// The zeroed size box. - pub const ZERO: SizeBox = SizeBox { - left: Size::ZERO, - top: Size::ZERO, - right: Size::ZERO, - bottom: Size::ZERO, - }; -} - impl ValueBox { /// Create a new box from four sizes. pub fn new(left: T, top: T, right: T, bottom: T) -> ValueBox { @@ -263,9 +342,10 @@ impl ValueBox { ValueBox { left: value, top: value, right: value, bottom: value } } - /// Get a mutable reference to the value for the specified direction and - /// alignment. Center alignment will be treated the same as origin + /// Get a mutable reference to the value for the specified direction at the /// alignment. + /// + /// Center alignment is treated the same as origin alignment. pub fn get_mut(&mut self, mut direction: Direction, alignment: Alignment) -> &mut T { if alignment == End { direction = direction.inv(); @@ -297,49 +377,33 @@ impl ValueBox { } } -impl Display for ValueBox where T: std::fmt::Debug { +impl Display for ValueBox where T: Display { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "[left: {}, top: {}, right: {}, bottom: {}]", + self.left, self.top, self.right, self.bottom) + } +} + +impl Debug for ValueBox where T: Debug { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "[left: {:?}, top: {:?}, right: {:?}, bottom: {:?}]", self.left, self.top, self.right, self.bottom) } } -debug_display!(ValueBox; T where T: std::fmt::Debug + Copy); +/// A size in four dimensions. +pub type SizeBox = ValueBox; -/// Either an absolute size or a factor of some metric. -#[derive(Copy, Clone, PartialEq)] -pub enum ScaleSize { - Absolute(Size), - Scaled(f32), +impl SizeBox { + /// The zeroed size box. + pub const ZERO: SizeBox = SizeBox { + left: Size::ZERO, + top: Size::ZERO, + right: Size::ZERO, + bottom: Size::ZERO, + }; } -/// A scale size that is scaled by the font size. -pub type FSize = ScaleSize; - -/// A scale size that is scaled by the size of the padded parent container. -pub type PSize = ScaleSize; - -impl ScaleSize { - /// Use the absolute value or scale the entity. - pub fn scaled(&self, entity: Size) -> Size { - match self { - ScaleSize::Absolute(s) => *s, - ScaleSize::Scaled(s) => *s * entity, - } - } -} - -impl Display for ScaleSize { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - ScaleSize::Absolute(size) => write!(f, "{}", size), - ScaleSize::Scaled(scale) => write!(f, "x{}", scale), - } - } -} - -debug_display!(ScaleSize); - /// An error which can be returned when parsing a size. pub struct ParseSizeError; diff --git a/src/style.rs b/src/style.rs index eb0de5daa..df5e13d18 100644 --- a/src/style.rs +++ b/src/style.rs @@ -26,11 +26,11 @@ pub struct TextStyle { /// The font scale to apply on the base font size. pub font_scale: f32, /// The word spacing (as a multiple of the font size). - pub word_spacing: f32, + pub word_spacing_scale: f32, /// The line spacing (as a multiple of the font size). - pub line_spacing: f32, + pub line_spacing_scale: f32, /// The paragraphs spacing (as a multiple of the font size). - pub paragraph_spacing: f32, + pub paragraph_spacing_scale: f32, } impl TextStyle { @@ -39,6 +39,21 @@ impl TextStyle { self.base_font_size * self.font_scale } + /// The absolute word spacing. + pub fn word_spacing(&self) -> Size { + self.word_spacing_scale * self.font_size() + } + + /// The absolute line spacing. + pub fn line_spacing(&self) -> Size { + (self.line_spacing_scale - 1.0) * self.font_size() + } + + /// The absolute paragraph spacing. + pub fn paragraph_spacing(&self) -> Size { + (self.paragraph_spacing_scale - 1.0) * self.font_size() + } + /// Toggle a class. /// /// If the class was one of _italic_ or _bold_, then: @@ -82,9 +97,9 @@ impl Default for TextStyle { fallback: vec![Serif], base_font_size: Size::pt(11.0), font_scale: 1.0, - word_spacing: 0.25, - line_spacing: 1.2, - paragraph_spacing: 1.5, + word_spacing_scale: 0.25, + line_spacing_scale: 1.2, + paragraph_spacing_scale: 1.5, } } } diff --git a/tests/layouts/align.typ b/tests/layouts/align.typ deleted file mode 100644 index a71019321..000000000 --- a/tests/layouts/align.typ +++ /dev/null @@ -1,23 +0,0 @@ -[box][ - A short sentence. [align: right][words.] - - A short sentence. [n] [align: right][words.] - - A short sentence. [par.break] [align: right][words.] - - [align: bottom] - A longer sentence with a few more words. -] - -[page.break] - -[box][ - [align: vertical=top] - Top - - [align: vertical=center] - Center - - [align: vertical=bottom] - Bottom [n] Bottom [n] Bottom -] diff --git a/tests/layouts/coma.typ b/tests/layouts/coma.typ index d0a02b74b..14f639b50 100644 --- a/tests/layouts/coma.typ +++ b/tests/layouts/coma.typ @@ -2,19 +2,19 @@ [page.margins: 1cm] [box][ - *Technical University Berlin* [n] - *Faculty II, Institute for Mathematics* [n] - Secretary Example [n] - Prof. Dr. Example [n] - Assistant #1, Assistant #2, Assistant #3 + *Technische Universität Berlin* [n] + *Fakultät II, Institut for Mathematik* [n] + Sekretariat MA [n] + Dr. Max Mustermann [n] + Ola Nordmann, John Doe ] -[align: right][*WiSe 2019/2020* [n] Week 1] +[align: right][*WiSe 2019/2020* [n] Woche 3] [v: 6mm] [align: center][ - *3. Ubungsblatt Computerorientierte Mathematik II* [v: 0.3mm] - *Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) [v: 0.3mm] + *3. Ubungsblatt Computerorientierte Mathematik II* [v: 2mm] + *Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) [v: 2mm] *Alle Antworten sind zu beweisen.* ] diff --git a/tests/layouts/lines.typ b/tests/layouts/lines.typ deleted file mode 100644 index 705de6389..000000000 --- a/tests/layouts/lines.typ +++ /dev/null @@ -1,14 +0,0 @@ -[page.size: height=5cm] - -Line. - -[box][ - Lines with [box][two] [box][boxes]. - - Lines with two boxes. -] -[box][ - Lines without two boxes. - - Lines without two boxes. -]