diff options
| author | Laurenz <laurmaedje@gmail.com> | 2020-01-26 15:51:13 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2020-01-26 15:51:13 +0100 |
| commit | 20fb4e7c379b79b84d9884d5f2c89d781c5793e2 (patch) | |
| tree | a1eef90680afa2b43cb1ce0a687c837fd78810e7 /src/layout | |
| parent | 0a087cd28bbee5fcdffbb9d49b0ba9f413ad7f92 (diff) | |
Document everything 📜
Diffstat (limited to 'src/layout')
| -rw-r--r-- | src/layout/actions.rs | 40 | ||||
| -rw-r--r-- | src/layout/line.rs | 52 | ||||
| -rw-r--r-- | src/layout/mod.rs | 231 | ||||
| -rw-r--r-- | src/layout/model.rs | 75 | ||||
| -rw-r--r-- | src/layout/stack.rs | 34 | ||||
| -rw-r--r-- | src/layout/text.rs | 29 |
6 files changed, 296 insertions, 165 deletions
diff --git a/src/layout/actions.rs b/src/layout/actions.rs index 710d92b4..b61f2201 100644 --- a/src/layout/actions.rs +++ b/src/layout/actions.rs @@ -1,4 +1,4 @@ -//! Drawing and cofiguration actions composing layouts. +//! Drawing and configuration actions composing layouts. use std::io::{self, Write}; use std::fmt::{self, Display, Formatter}; @@ -9,14 +9,15 @@ use super::{Layout, Serialize}; use self::LayoutAction::*; -/// A layouting action. +/// A layouting action, which is the basic building block layouts are composed +/// of. #[derive(Clone)] pub enum LayoutAction { /// Move to an absolute position. MoveAbsolute(Size2D), - /// Set the font by index and font size. + /// Set the font given the index from the font loader and font size. SetFont(FontIndex, Size), - /// Write text starting at the current position. + /// Write text at the current position. WriteText(String), /// Visualize a box for debugging purposes. DebugBox(Size2D), @@ -50,17 +51,18 @@ debug_display!(LayoutAction); /// A sequence of layouting actions. /// /// The sequence of actions is optimized as the actions are added. For example, -/// a font changing option will only be added if the selected font is not already active. -/// All configuration actions (like moving, setting fonts, ...) are only flushed when -/// content is written. +/// a font changing option will only be added if the selected font is not +/// already active. All configuration actions (like moving, setting fonts, ...) +/// are only flushed when content is written. /// -/// Furthermore, the action list can translate absolute position into a coordinate system -/// with a different origin. This is realized in the `add_box` method, which allows a layout to -/// be added at a position, effectively translating all movement actions inside the layout -/// by the position. +/// Furthermore, the action list can translate absolute position into a +/// coordinate system with a different origin. This is realized in the +/// `add_layout` method, which allows a layout to be added at a position, +/// effectively translating all movement actions inside the layout by the +/// position. #[derive(Debug, Clone)] pub struct LayoutActions { - pub origin: Size2D, + origin: Size2D, actions: Vec<LayoutAction>, active_font: (FontIndex, Size), next_pos: Option<Size2D>, @@ -97,15 +99,14 @@ impl LayoutActions { } /// Add a series of actions. - pub fn extend<I>(&mut self, actions: I) - where I: IntoIterator<Item = LayoutAction> { + pub fn extend<I>(&mut self, actions: I) where I: IntoIterator<Item = LayoutAction> { for action in actions.into_iter() { self.add(action); } } - /// Add a layout at a position. All move actions inside the layout are translated - /// by the position. + /// Add a layout at a position. All move actions inside the layout are + /// translated by the position. pub fn add_layout(&mut self, position: Size2D, layout: Layout) { self.flush_position(); @@ -120,10 +121,9 @@ impl LayoutActions { self.actions.is_empty() } - /// Return the list of actions as a vector, leaving an empty - /// vector in its position. - pub fn to_vec(&mut self) -> Vec<LayoutAction> { - std::mem::replace(&mut self.actions, vec![]) + /// Return the list of actions as a vector. + pub fn into_vec(self) -> Vec<LayoutAction> { + self.actions } /// Append a cached move action if one is cached. diff --git a/src/layout/line.rs b/src/layout/line.rs index 5e0839b1..2c8e45f2 100644 --- a/src/layout/line.rs +++ b/src/layout/line.rs @@ -1,9 +1,18 @@ +//! The line layouter arranges boxes into lines. +//! +//! Along the primary axis, the boxes are laid out next to each other while they +//! fit into a line. When a line break is necessary, the line is finished and a +//! new line is started offset on the secondary axis by the height of previous +//! line and the extra line spacing. +//! +//! Internally, the line layouter uses a stack layouter to arrange the finished +//! lines. + use super::stack::{StackLayouter, StackContext}; 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. +/// Performs the line layouting. #[derive(Debug, Clone)] pub struct LineLayouter { /// The context for layouting. @@ -34,7 +43,9 @@ pub struct LineContext { pub line_spacing: Size, } -/// A simple line of boxes. +/// A line run is a sequence of boxes with the same alignment that are arranged +/// in a line. A real line can consist of multiple runs with different +/// alignments. #[derive(Debug, Clone)] struct LineRun { /// The so-far accumulated layouts in the line. @@ -43,9 +54,13 @@ struct LineRun { /// line. size: Size2D, /// The alignment of all layouts in the line. + /// + /// When a new run is created the alignment is yet to be determined. Once a + /// layout is added, it is decided which alignment the run has and all + /// further elements of the run must have this alignment. alignment: Option<LayoutAlignment>, - /// The remaining usable space if another differently aligned line run - /// already took up some space. + /// If another line run with different alignment already took up some space + /// of the line, this run has less space and how much is stored here. usable: Option<Size>, /// A possibly cached soft spacing or spacing state. last_spacing: LastSpacing, @@ -137,7 +152,10 @@ impl LineLayouter { } } - /// The remaining usable size in the run. + /// The remaining usable size of the run. + /// + /// This specifies how much more fits before a line break needs to be + /// issued. fn usable(&self) -> Size2D { // The base is the usable space per stack layouter. let mut usable = self.stack.usable().generalized(self.ctx.axes); @@ -152,7 +170,7 @@ impl LineLayouter { usable } - /// Add primary spacing to the line. + /// Add spacing along the primary axis 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. @@ -178,20 +196,20 @@ impl LineLayouter { } } - /// Finish the run and add secondary spacing to the underlying stack. + /// Finish the line and add secondary spacing to the underlying stack. pub fn add_secondary_spacing(&mut self, spacing: Size, kind: SpacingKind) { self.finish_line_if_not_empty(); self.stack.add_spacing(spacing, kind) } - /// Change the layouting axes used by this layouter. + /// Update the layouting axes used by this layouter. pub fn set_axes(&mut self, axes: LayoutAxes) { self.finish_line_if_not_empty(); self.ctx.axes = axes; self.stack.set_axes(axes) } - /// Change the layouting spaces to use. + /// Update 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 @@ -200,12 +218,14 @@ impl LineLayouter { self.stack.set_spaces(spaces, replace_empty && self.line_is_empty()); } - /// Change the line spacing. + /// Update the line spacing. pub fn set_line_spacing(&mut self, line_spacing: Size) { self.ctx.line_spacing = line_spacing; } - /// The remaining unpadded, unexpanding spaces. + /// The remaining inner layout spaces. Inner means, that padding is already + /// subtracted and the spaces are unexpanding. This can be used to signal + /// a function how much space it has to layout itself. pub fn remaining(&self) -> LayoutSpaces { let mut spaces = self.stack.remaining(); *spaces[0].dimensions.get_secondary_mut(self.ctx.axes) @@ -218,19 +238,21 @@ impl LineLayouter { self.run.size == Size2D::ZERO && self.run.layouts.is_empty() } - /// Finish the last line and compute the final multi-layout. + /// Finish the last line and compute the final list of boxes. pub fn finish(mut self) -> MultiLayout { self.finish_line_if_not_empty(); self.stack.finish() } /// Finish the currently active space and start a new one. + /// + /// At the top level, this is a page break. pub fn finish_space(&mut self, hard: bool) { self.finish_line_if_not_empty(); self.stack.finish_space(hard) } - /// Add the current line to the stack and start a new line. + /// Finish the line and start a new one. pub fn finish_line(&mut self) { let mut actions = LayoutActions::new(); @@ -251,7 +273,7 @@ impl LineLayouter { dimensions: self.run.size.specialized(self.ctx.axes), alignment: self.run.alignment .unwrap_or(LayoutAlignment::new(Origin, Origin)), - actions: actions.to_vec(), + actions: actions.into_vec(), }); self.run = LineRun::new(); diff --git a/src/layout/mod.rs b/src/layout/mod.rs index f8074524..bcabf7f3 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,4 +1,4 @@ -//! The core layouting engine. +//! Layouting types and engines. use std::io::{self, Write}; use std::fmt::{self, Display, Formatter}; @@ -6,7 +6,7 @@ use smallvec::SmallVec; use toddle::query::FontIndex; use crate::size::{Size, Size2D, SizeBox}; -use self::{GenericAxis::*, SpecificAxis::*, Direction::*, Alignment::*}; +use self::prelude::*; pub mod line; pub mod stack; @@ -15,8 +15,13 @@ pub mod text; pub_use_mod!(actions); pub_use_mod!(model); +/// Basic types used across the layouting engine. pub mod prelude { - pub use super::{LayoutSpace, LayoutExpansion, LayoutAxes, LayoutAlignment}; + pub use super::{ + LayoutContext, layout, LayoutSpace, + Layouted, Commands, + LayoutAxes, LayoutAlignment, LayoutExpansion + }; pub use super::GenericAxis::{self, *}; pub use super::SpecificAxis::{self, *}; pub use super::Direction::{self, *}; @@ -27,7 +32,7 @@ pub mod prelude { /// A collection of layouts. pub type MultiLayout = Vec<Layout>; -/// A sequence of layouting actions inside a box. +/// A finished box with content at fixed positions. #[derive(Debug, Clone)] pub struct Layout { /// The size of the box. @@ -81,10 +86,11 @@ impl Serialize for MultiLayout { } } -/// A possibly stack-allocated vector of layout spaces. +/// A vector of layout spaces, that is stack allocated as long as it only +/// contains at most 2 spaces. pub type LayoutSpaces = SmallVec<[LayoutSpace; 2]>; -/// Spacial layouting constraints. +/// The space into which content is laid out. #[derive(Debug, Copy, Clone)] pub struct LayoutSpace { /// The maximum size of the box to layout in. @@ -92,8 +98,7 @@ pub struct LayoutSpace { /// Padding that should be respected on each side. pub padding: SizeBox, /// Whether to expand the dimensions of the resulting layout to the full - /// dimensions of this space or to shrink them to fit the content for the - /// horizontal and vertical axis. + /// dimensions of this space or to shrink them to fit the content. pub expansion: LayoutExpansion, } @@ -119,70 +124,12 @@ impl LayoutSpace { } } -/// Whether to fit to content or expand to the space's size. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct LayoutExpansion { - pub horizontal: bool, - pub vertical: bool, -} - -impl LayoutExpansion { - pub fn new(horizontal: bool, vertical: bool) -> LayoutExpansion { - LayoutExpansion { horizontal, vertical } - } - - /// Borrow the specified component mutably. - pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut bool { - match axis { - Horizontal => &mut self.horizontal, - Vertical => &mut self.vertical, - } - } -} - -/// The axes along which the content is laid out. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct LayoutAxes { - pub primary: Direction, - pub secondary: Direction, -} - -impl LayoutAxes { - pub fn new(primary: Direction, secondary: Direction) -> LayoutAxes { - if primary.axis() == secondary.axis() { - panic!("LayoutAxes::new: invalid aligned axes {:?} and {:?}", - primary, secondary); - } - - LayoutAxes { primary, secondary } - } - - /// Return the direction of the specified generic axis. - pub fn get(self, axis: GenericAxis) -> Direction { - match axis { - Primary => self.primary, - Secondary => self.secondary, - } - } - - /// Borrow the direction of the specified generic axis mutably. - pub fn get_mut(&mut self, axis: GenericAxis) -> &mut Direction { - match axis { - Primary => &mut self.primary, - Secondary => &mut self.secondary, - } - } - - /// Return the direction of the specified specific axis. - pub fn get_specific(self, axis: SpecificAxis) -> Direction { - self.get(axis.to_generic(self)) - } -} - /// The two generic layouting axes. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum GenericAxis { + /// The primary axis along which words are laid out. Primary, + /// The secondary axis along which lines and paragraphs are laid out. Secondary, } @@ -191,14 +138,6 @@ impl GenericAxis { pub fn to_specific(self, axes: LayoutAxes) -> SpecificAxis { axes.get(self).axis() } - - /// The other axis. - pub fn inv(self) -> GenericAxis { - match self { - Primary => Secondary, - Secondary => Primary, - } - } } impl Display for GenericAxis { @@ -213,7 +152,9 @@ impl Display for GenericAxis { /// The two specific layouting axes. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum SpecificAxis { + /// The horizontal layouting axis. Horizontal, + /// The vertical layouting axis. Vertical, } @@ -222,14 +163,6 @@ impl SpecificAxis { pub fn to_generic(self, axes: LayoutAxes) -> GenericAxis { if self == axes.primary.axis() { Primary } else { Secondary } } - - /// The other axis. - pub fn inv(self) -> SpecificAxis { - match self { - Horizontal => Vertical, - Vertical => Horizontal, - } - } } impl Display for SpecificAxis { @@ -241,8 +174,50 @@ impl Display for SpecificAxis { } } +/// Specifies along which directions content is laid out. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct LayoutAxes { + /// The primary layouting direction. + pub primary: Direction, + /// The secondary layouting direction. + pub secondary: Direction, +} + +impl LayoutAxes { + /// Create a new instance from the two values. + /// + /// # Panics + /// This function panics if the directions are aligned, that is, they are + /// on the same axis. + pub fn new(primary: Direction, secondary: Direction) -> LayoutAxes { + if primary.axis() == secondary.axis() { + panic!("LayoutAxes::new: invalid aligned axes \ + {} and {}", primary, secondary); + } + + LayoutAxes { primary, secondary } + } + + /// Return the direction of the specified generic axis. + pub fn get(self, axis: GenericAxis) -> Direction { + match axis { + Primary => self.primary, + Secondary => self.secondary, + } + } + + /// Borrow the direction of the specified generic axis mutably. + pub fn get_mut(&mut self, axis: GenericAxis) -> &mut Direction { + match axis { + Primary => &mut self.primary, + Secondary => &mut self.secondary, + } + } +} + /// Directions along which content is laid out. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[allow(missing_docs)] pub enum Direction { LeftToRight, RightToLeft, @@ -260,6 +235,8 @@ impl Direction { } /// Whether this axis points into the positive coordinate direction. + /// + /// The positive directions are left-to-right and top-to-bottom. pub fn is_positive(self) -> bool { match self { LeftToRight | TopToBottom => true, @@ -267,6 +244,14 @@ impl Direction { } } + /// The factor for this direction. + /// + /// - `1` if the direction is positive. + /// - `-1` if the direction is negative. + pub fn factor(self) -> i32 { + if self.is_positive() { 1 } else { -1 } + } + /// The inverse axis. pub fn inv(self) -> Direction { match self { @@ -276,14 +261,6 @@ impl Direction { BottomToTop => TopToBottom, } } - - /// The factor for this direction. - /// - /// - `1` if the direction is positive. - /// - `-1` if the direction is negative. - pub fn factor(self) -> i32 { - if self.is_positive() { 1 } else { -1 } - } } impl Display for Direction { @@ -297,18 +274,29 @@ impl Display for Direction { } } -/// Where to align a layout in a container. +/// Specifies where to align a layout in a parent container. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct LayoutAlignment { + /// The alignment along the primary axis. pub primary: Alignment, + /// The alignment along the secondary axis. pub secondary: Alignment, } impl LayoutAlignment { + /// Create a new instance from the two values. pub fn new(primary: Alignment, secondary: Alignment) -> LayoutAlignment { LayoutAlignment { primary, secondary } } + /// Return the alignment of the specified generic axis. + pub fn get(self, axis: GenericAxis) -> Alignment { + match axis { + Primary => self.primary, + Secondary => self.secondary, + } + } + /// Borrow the alignment of the specified generic axis mutably. pub fn get_mut(&mut self, axis: GenericAxis) -> &mut Alignment { match axis { @@ -321,8 +309,11 @@ impl LayoutAlignment { /// Where to align content. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum Alignment { + /// Align content at the start of the axis. Origin, + /// Align content centered on the axis. Center, + /// Align content at the end of the axis. End, } @@ -337,12 +328,53 @@ impl Alignment { } } -/// Whitespace between boxes with different interaction properties. +/// Specifies whether to expand a layout to the full size of the space it is +/// laid out in or to shrink it to fit the content. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct LayoutExpansion { + /// Whether to expand on the horizontal axis. + pub horizontal: bool, + /// Whether to expand on the vertical axis. + pub vertical: bool, +} + +impl LayoutExpansion { + /// Create a new instance from the two values. + pub fn new(horizontal: bool, vertical: bool) -> LayoutExpansion { + LayoutExpansion { horizontal, vertical } + } + + /// Return the expansion value for the given specific axis. + pub fn get(self, axis: SpecificAxis) -> bool { + match axis { + Horizontal => self.horizontal, + Vertical => self.vertical, + } + } + + /// Borrow the expansion value for the given specific axis mutably. + pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut bool { + match axis { + Horizontal => &mut self.horizontal, + Vertical => &mut self.vertical, + } + } +} + +/// Defines how a given spacing interacts with (possibly existing) surrounding +/// spacing. +/// +/// There are two options for interaction: Hard and soft spacing. Typically, +/// hard spacing is used when a fixed amount of space needs to be inserted no +/// matter what. In contrast, soft spacing can be used to insert a default +/// spacing between e.g. two words or paragraphs that can still be overridden by +/// a hard space. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum SpacingKind { - /// A hard space consumes surrounding soft spaces and is always layouted. + /// Hard spaces are always laid out and consume surrounding soft space. Hard, - /// A soft space consumes surrounding soft spaces with higher value. + /// Soft spaces are not laid out if they are touching a hard space and + /// consume neighbouring soft spaces with higher levels. Soft(u32), } @@ -357,11 +389,16 @@ impl SpacingKind { pub const WORD: SpacingKind = SpacingKind::Soft(1); } -/// The last appeared spacing. +/// The spacing kind of the most recently inserted item in a layouting process. +/// This is not about the last _spacing item_, but the last _item_, which is why +/// this can be `None`. #[derive(Debug, Copy, Clone, PartialEq)] enum LastSpacing { + /// The last item was hard spacing. Hard, + /// The last item was soft spacing with the given width and level. Soft(Size, u32), + /// The last item was not spacing. None, } diff --git a/src/layout/model.rs b/src/layout/model.rs index 2e61b453..1d635f5c 100644 --- a/src/layout/model.rs +++ b/src/layout/model.rs @@ -1,3 +1,7 @@ +//! The model layouter layouts models (i.e. +//! [syntax models](crate::syntax::SyntaxModel) and [functions](crate::func)) +//! by executing commands issued by the models. + use std::future::Future; use std::pin::Pin; use smallvec::smallvec; @@ -13,7 +17,7 @@ use super::text::{layout_text, TextContext}; use super::*; -#[derive(Debug, Clone)] +/// Performs the model layouting. pub struct ModelLayouter<'a, 'p> { ctx: LayoutContext<'a, 'p>, layouter: LineLayouter, @@ -21,7 +25,7 @@ pub struct ModelLayouter<'a, 'p> { errors: Errors, } -/// The general context for layouting. +/// The context for layouting. #[derive(Debug, Clone)] pub struct LayoutContext<'a, 'p> { /// The font loader to retrieve fonts from when typesetting text @@ -46,53 +50,74 @@ pub struct LayoutContext<'a, 'p> { pub debug: bool, } +/// The result of layouting: Some layouted things and a list of errors. pub struct Layouted<T> { + /// The result of the layouting process. pub output: T, + /// Errors that arose in the process of layouting. pub errors: Errors, } -impl<T> Layouted<T> { - pub fn map<F, U>(self, f: F) -> Layouted<U> where F: FnOnce(T) -> U { - Layouted { - output: f(self.output), - errors: self.errors, - } - } -} - /// A sequence of layouting commands. pub type Commands<'a> = Vec<Command<'a>>; -/// Layouting commands from functions to the typesetting engine. +/// Commands issued to the layouting engine by models. #[derive(Debug)] pub enum Command<'a> { + /// Layout the given model in the current context (i.e. not nested). The + /// content of the model is not laid out into a separate box and then added, + /// but simply laid out flat in the active layouting process. + /// + /// This has the effect that the content fits nicely into the active line + /// layouting, enabling functions to e.g. change the style of some piece of + /// text while keeping it integrated in the current paragraph. LayoutSyntaxModel(&'a SyntaxModel), + /// Add a already computed layout. Add(Layout), + /// Add multiple layouts, one after another. This is equivalent to multiple + /// [Add](Command::Add) commands. AddMultiple(MultiLayout), + + /// Add spacing of given [kind](super::SpacingKind) along the primary or + /// secondary axis. The spacing kind defines how the spacing interacts with + /// surrounding spacing. AddSpacing(Size, SpacingKind, GenericAxis), - FinishLine, - FinishSpace, + /// Start a new line. + BreakLine, + /// Start a new paragraph. BreakParagraph, + /// Start a new page, which will exist in the finished layout even if it + /// stays empty (since the page break is a _hard_ space break). BreakPage, + /// Update the text style. SetTextStyle(TextStyle), + /// Update the page style. SetPageStyle(PageStyle), + + /// Update the alignment for future boxes added to this layouting process. SetAlignment(LayoutAlignment), + /// Update the layouting axes along which future boxes will be laid out. + /// This finishes the current line. SetAxes(LayoutAxes), } +/// Layout a syntax model into a list of boxes. pub async fn layout(model: &SyntaxModel, ctx: LayoutContext<'_, '_>) -> Layouted<MultiLayout> { let mut layouter = ModelLayouter::new(ctx); layouter.layout_syntax_model(model).await; layouter.finish() } +/// A dynamic future type which allows recursive invocation of async functions +/// when used as the return type. This is also how the async trait functions +/// work internally. pub type DynFuture<'a, T> = Pin<Box<dyn Future<Output=T> + 'a>>; impl<'a, 'p> ModelLayouter<'a, 'p> { - /// Create a new syntax tree layouter. + /// Create a new model layouter. pub fn new(ctx: LayoutContext<'a, 'p>) -> ModelLayouter<'a, 'p> { ModelLayouter { layouter: LineLayouter::new(LineContext { @@ -109,10 +134,12 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { } } + /// Flatly layout a model into this layouting process. pub fn layout<'r>( &'r mut self, model: Spanned<&'r dyn Model> ) -> DynFuture<'r, ()> { Box::pin(async move { + // Execute the model's layout function which generates the commands. let layouted = model.v.layout(LayoutContext { style: &self.style, spaces: self.layouter.remaining(), @@ -121,14 +148,16 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { .. self.ctx }).await; - let commands = layouted.output; + // Add the errors generated by the model to the error list. self.errors.extend(offset_spans(layouted.errors, model.span.start)); - for command in commands { + for command in layouted.output { self.execute_command(command, model.span).await; } }) } + /// Layout a syntax model by directly processing the nodes instead of using + /// the command based architecture. pub fn layout_syntax_model<'r>( &'r mut self, model: &'r SyntaxModel @@ -162,6 +191,7 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { } }) } + /// Compute the finished list of boxes. pub fn finish(self) -> Layouted<MultiLayout> { Layouted { output: self.layouter.finish(), @@ -169,6 +199,8 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { } } + /// Execute a command issued by a model. When the command is errorful, the + /// given span is stored with the error. fn execute_command<'r>( &'r mut self, command: Command<'r>, @@ -186,8 +218,7 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { Secondary => self.layouter.add_secondary_spacing(space, kind), } - FinishLine => self.layouter.finish_line(), - FinishSpace => self.layouter.finish_space(true), + BreakLine => self.layouter.finish_line(), BreakParagraph => self.layout_paragraph(), BreakPage => { if self.ctx.nested { @@ -209,6 +240,9 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { } else { self.style.page = style; + // The line layouter has no idea of page styles and thus we + // need to recompute the layouting space resulting of the + // new page style and update it within the layouter. let margins = style.margins(); self.ctx.base = style.dimensions.unpadded(margins); self.layouter.set_spaces(smallvec![ @@ -229,6 +263,7 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { } }) } + /// Layout a continous piece of text and add it to the line layouter. async fn layout_text(&mut self, text: &str) { self.layouter.add(layout_text(text, TextContext { loader: &self.ctx.loader, @@ -238,6 +273,7 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { }).await) } + /// Add the spacing for a syntactic space node. fn layout_space(&mut self) { self.layouter.add_primary_spacing( self.style.text.word_spacing(), @@ -245,6 +281,7 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { ); } + /// Finish the paragraph and add paragraph spacing. fn layout_paragraph(&mut self) { self.layouter.add_secondary_spacing( self.style.text.paragraph_spacing(), diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 96b44d04..8f76ccbf 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -1,10 +1,32 @@ +//! The stack layouter arranges boxes along the secondary layouting axis. +//! +//! Individual layouts can be aligned at origin / center / end on both axes and +//! these alignments are with respect to the growable layout space and not the +//! total possible size. +//! +//! This means that a later layout can have influence on the position of an +//! earlier one. Consider, for example, the following code: +//! ```typst +//! [align: right][A word.] +//! [align: left][A sentence with a couple more words.] +//! ``` +//! The resulting layout looks like this: +//! ```text +//! |--------------------------------------| +//! | A word. | +//! | | +//! | A sentence with a couple more words. | +//! |--------------------------------------| +//! ``` +//! The position of the first aligned box thus depends on the length of the +//! sentence in the second box. + use smallvec::smallvec; use crate::size::ValueBox; use super::*; -/// The stack layouter stack boxes onto each other along the secondary layouting -/// axis. +/// Performs the stack layouting. #[derive(Debug, Clone)] pub struct StackLayouter { /// The context for layouting. @@ -222,8 +244,8 @@ impl StackLayouter { } } - /// The remaining unpadded, unexpanding spaces. If a multi-layout is laid - /// out into these spaces, it will fit into this stack. + /// The remaining unpadded, unexpanding spaces. If a function is laid out + /// into these spaces, it will fit into this stack. pub fn remaining(&self) -> LayoutSpaces { let dimensions = self.usable(); @@ -257,7 +279,7 @@ impl StackLayouter { self.space.index == self.ctx.spaces.len() - 1 } - /// Compute the finished multi-layout. + /// Compute the finished list of boxes. pub fn finish(mut self) -> MultiLayout { if self.space.hard || !self.space_is_empty() { self.finish_space(false); @@ -373,7 +395,7 @@ impl StackLayouter { self.layouts.push(Layout { dimensions, alignment: self.ctx.alignment, - actions: actions.to_vec(), + actions: actions.into_vec(), }); // ------------------------------------------------------------------ // diff --git a/src/layout/text.rs b/src/layout/text.rs index eb598e2f..a0f47643 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -1,3 +1,9 @@ +//! The text layouter layouts continous pieces of text into boxes. +//! +//! The layouter picks the most suitable font for each individual character. +//! When the primary layouting axis horizontally inversed, the word is spelled +//! backwards. Vertical word layout is not yet supported. + use toddle::query::{SharedFontLoader, FontQuery, FontIndex}; use toddle::tables::{CharMap, Header, HorizontalMetrics}; @@ -6,7 +12,7 @@ use crate::style::TextStyle; use super::*; -/// Layouts text into boxes. +/// Performs the text layouting. struct TextLayouter<'a, 'p> { ctx: TextContext<'a, 'p>, text: &'a str, @@ -17,20 +23,22 @@ struct TextLayouter<'a, 'p> { } /// The context for text layouting. -/// -/// See [`LayoutContext`] for details about the fields. #[derive(Copy, Clone)] pub struct TextContext<'a, 'p> { + /// The font loader to retrieve fonts from when typesetting text + /// using [`layout_text`]. pub loader: &'a SharedFontLoader<'p>, + /// The style for text: Font selection with classes, weights and variants, + /// font sizes, spacing and so on. pub style: &'a TextStyle, + /// The axes along which the word is laid out. For now, only + /// primary-horizontal layouting is supported. pub axes: LayoutAxes, + /// The alignment of the finished layout. pub alignment: LayoutAlignment, } /// Layouts text into a box. -/// -/// There is no complex layout involved. The text is simply laid out left- -/// to-right using the correct font for each character. pub async fn layout_text(text: &str, ctx: TextContext<'_, '_>) -> Layout { TextLayouter::new(text, ctx).layout().await } @@ -48,8 +56,9 @@ impl<'a, 'p> TextLayouter<'a, 'p> { } } - /// Layout the text + /// Do the layouting. async fn layout(mut self) -> Layout { + // If the primary axis is negative, we layout the characters reversed. if self.ctx.axes.primary.is_positive() { for c in self.text.chars() { self.layout_char(c).await; @@ -60,6 +69,7 @@ impl<'a, 'p> TextLayouter<'a, 'p> { } } + // Flush the last buffered parts of the word. if !self.buffer.is_empty() { self.actions.add(LayoutAction::WriteText(self.buffer)); } @@ -67,7 +77,7 @@ impl<'a, 'p> TextLayouter<'a, 'p> { Layout { dimensions: Size2D::new(self.width, self.ctx.style.font_size()), alignment: self.ctx.alignment, - actions: self.actions.to_vec(), + actions: self.actions.into_vec(), } } @@ -81,6 +91,8 @@ impl<'a, 'p> TextLayouter<'a, 'p> { self.width += char_width; + // Flush the buffer and issue a font setting action if the font differs + // from the last character's one. if self.active_font != index { if !self.buffer.is_empty() { let text = std::mem::replace(&mut self.buffer, String::new()); @@ -106,6 +118,7 @@ impl<'a, 'p> TextLayouter<'a, 'p> { }; if let Some((font, index)) = loader.get(query).await { + // Determine the width of the char. let header = font.read_table::<Header>().ok()?; let font_unit_ratio = 1.0 / (header.units_per_em as f32); let font_unit_to_size = |x| Size::pt(font_unit_ratio * x); |
