summaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-01-26 15:51:13 +0100
committerLaurenz <laurmaedje@gmail.com>2020-01-26 15:51:13 +0100
commit20fb4e7c379b79b84d9884d5f2c89d781c5793e2 (patch)
treea1eef90680afa2b43cb1ce0a687c837fd78810e7 /src/layout
parent0a087cd28bbee5fcdffbb9d49b0ba9f413ad7f92 (diff)
Document everything 📜
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/actions.rs40
-rw-r--r--src/layout/line.rs52
-rw-r--r--src/layout/mod.rs231
-rw-r--r--src/layout/model.rs75
-rw-r--r--src/layout/stack.rs34
-rw-r--r--src/layout/text.rs29
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);