summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-12-30 22:28:56 +0100
committerLaurenz <laurmaedje@gmail.com>2019-12-30 22:28:56 +0100
commit269f069a4d721a986807293ef71be1348bfae3d4 (patch)
tree31b737c4ff2aead3eb5e2673828595bb26622032 /src
parentb8620121a692df6313eeb5ccf7baf89c1e364116 (diff)
Simple line layouter 🧾
Diffstat (limited to 'src')
-rw-r--r--src/func/macros.rs3
-rw-r--r--src/func/mod.rs1
-rw-r--r--src/layout/flex.rs198
-rw-r--r--src/layout/line.rs283
-rw-r--r--src/layout/mod.rs9
-rw-r--r--src/layout/stack.rs100
-rw-r--r--src/layout/text.rs44
-rw-r--r--src/layout/tree.rs57
-rw-r--r--src/library/boxed.rs14
-rw-r--r--src/size.rs244
-rw-r--r--src/style.rs27
11 files changed, 563 insertions, 417 deletions
diff --git a/src/func/macros.rs b/src/func/macros.rs
index df795213..2da219bc 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 48e70034..208e1977 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 72878a29..00000000
--- 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<FlexUnit>,
- 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<MultiLayout> {
- 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<Size2D> {
- 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<Size2D> {
- 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 00000000..5aae7dcd
--- /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<LayoutAlignment>,
+ /// The remaining usable space if another differently aligned line run
+ /// already took up some space.
+ usable: Option<Size>,
+ /// 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<MultiLayout> {
+ 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 f937a054..53c3e91e 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 3c659d8a..e0562672 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<Alignment>,
/// 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 996c5139..e9721429 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<Layout> {
- 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 7910fdd3..db59ca8d 100644
--- a/src/layout/tree.rs
+++ b/src/layout/tree.rs
@@ -16,7 +16,7 @@ pub fn layout(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiLayout
#[derive(Debug, Clone)]
struct TreeLayouter<'a, 'p> {
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<MultiLayout> {
- 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 ece8b20f..7333c50f 100644
--- a/src/library/boxed.rs
+++ b/src/library/boxed.rs
@@ -9,28 +9,30 @@ function! {
pub struct Boxed {
body: SyntaxTree,
map: ExtentMap<PSize>,
- debug: bool,
+ debug: Option<bool>,
}
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::<bool>("debug")?.unwrap_or(true),
+ debug: args.get_key_opt::<bool>("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 09096783..a3380f34 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,
+ }
+ }
+}
+
+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 from two sizes.
- pub fn new(x: Size, y: Size) -> Size2D { Size2D { x, y } }
+debug_display!(ScaleSize);
- /// 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 } }
+/// 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;
+
+/// A value in two dimensions.
+#[derive(Default, Copy, Clone, PartialEq)]
+pub struct Value2D<T: Copy> {
+ /// The horizontal component.
+ pub x: T,
+ /// The vertical component.
+ pub y: T,
+}
- /// 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 } }
+impl<T: Copy> Value2D<T> {
+ /// Create a new 2D-value from two values.
+ pub fn new(x: T, y: T) -> Value2D<T> { Value2D { x, y } }
- /// 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 } }
+ /// Create a new 2D-value with `x` set to a value and `y` to default.
+ pub fn with_x(x: T) -> Value2D<T> 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<T> 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<T> 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<T> 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<T> { 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<T> {
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<T> {
// 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<T: Copy> Display for Value2D<T> where T: Display {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "[{}, {}]", self.x, self.y)
+ }
+}
+
+impl<T: Copy> Debug for Value2D<T> 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<Size>;
+
+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<Size>;
+/// A value that is stretchable in an interval from a minimal through an optimal
+/// to a maximal value.
+pub struct StretchValue<T: Copy> {
+ /// 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<T: Copy> StretchValue<T> {
+ /// Create a new stretch size from minimum, optimal and maximum values.
+ pub fn new(min: T, opt: T, max: T) -> StretchValue<T> {
+ StretchValue { min, opt, max }
+ }
+}
+
+impl<T: Copy> Display for StretchValue<T> where T: Display {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "({}, {}, {})", self.min, self.opt, self.max)
+ }
+}
+
+impl<T: Copy> Debug for StretchValue<T> 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<Size>;
/// A value in four dimensions.
-#[derive(Copy, Clone, PartialEq)]
+#[derive(Default, Copy, Clone, PartialEq)]
pub struct ValueBox<T: Copy> {
/// The left extent.
pub left: T,
@@ -242,16 +331,6 @@ pub struct ValueBox<T: Copy> {
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<T: Copy> ValueBox<T> {
/// Create a new box from four sizes.
pub fn new(left: T, top: T, right: T, bottom: T) -> ValueBox<T> {
@@ -263,9 +342,10 @@ impl<T: Copy> ValueBox<T> {
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,48 +377,32 @@ impl<T: Copy> ValueBox<T> {
}
}
-impl<T: Copy> Display for ValueBox<T> where T: std::fmt::Debug {
+impl<T: Copy> Display for ValueBox<T> where T: Display {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "[left: {:?}, top: {:?}, right: {:?}, bottom: {:?}]",
+ write!(f, "[left: {}, top: {}, right: {}, bottom: {}]",
self.left, self.top, self.right, self.bottom)
}
}
-debug_display!(ValueBox; T where T: std::fmt::Debug + Copy);
-
-/// Either an absolute size or a factor of some metric.
-#[derive(Copy, Clone, PartialEq)]
-pub enum ScaleSize {
- Absolute(Size),
- Scaled(f32),
-}
-
-/// 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 {
+impl<T: Copy> Debug for ValueBox<T> where T: Debug {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self {
- ScaleSize::Absolute(size) => write!(f, "{}", size),
- ScaleSize::Scaled(scale) => write!(f, "x{}", scale),
- }
+ write!(f, "[left: {:?}, top: {:?}, right: {:?}, bottom: {:?}]",
+ self.left, self.top, self.right, self.bottom)
}
}
-debug_display!(ScaleSize);
+/// A size in four dimensions.
+pub type SizeBox = ValueBox<Size>;
+
+impl SizeBox {
+ /// The zeroed size box.
+ pub const ZERO: SizeBox = SizeBox {
+ left: Size::ZERO,
+ top: Size::ZERO,
+ right: Size::ZERO,
+ bottom: Size::ZERO,
+ };
+}
/// An error which can be returned when parsing a size.
pub struct ParseSizeError;
diff --git a/src/style.rs b/src/style.rs
index eb0de5da..df5e13d1 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,
}
}
}