diff options
Diffstat (limited to 'src/layout')
| -rw-r--r-- | src/layout/elements.rs | 46 | ||||
| -rw-r--r-- | src/layout/line.rs | 122 | ||||
| -rw-r--r-- | src/layout/mod.rs | 106 | ||||
| -rw-r--r-- | src/layout/primitive.rs | 91 | ||||
| -rw-r--r-- | src/layout/stack.rs | 122 | ||||
| -rw-r--r-- | src/layout/text.rs | 41 | ||||
| -rw-r--r-- | src/layout/tree.rs | 100 |
7 files changed, 302 insertions, 326 deletions
diff --git a/src/layout/elements.rs b/src/layout/elements.rs index 92b53ae8..b4a6b31c 100644 --- a/src/layout/elements.rs +++ b/src/layout/elements.rs @@ -1,19 +1,20 @@ -//! The elements layouts are composed of. +//! Basic building blocks of layouts. use std::fmt::{self, Debug, Formatter}; -use ttf_parser::GlyphId; use fontdock::FaceId; +use ttf_parser::GlyphId; + use crate::geom::Size; -/// A sequence of positioned layout elements. -#[derive(Debug, Clone, PartialEq)] +/// A collection of absolutely positioned layout elements. +#[derive(Debug, Default, Clone, PartialEq)] pub struct LayoutElements(pub Vec<(Size, LayoutElement)>); impl LayoutElements { - /// Create an empty sequence. + /// Create an new empty collection. pub fn new() -> Self { - LayoutElements(vec![]) + Self(vec![]) } /// Add an element at a position. @@ -21,7 +22,9 @@ impl LayoutElements { self.0.push((pos, element)); } - /// Add a sequence of elements offset by an `offset`. + /// Add all elements of another collection, offsetting each by the given + /// `offset`. This can be used to place a sublayout at a position in another + /// layout. pub fn extend_offset(&mut self, offset: Size, more: Self) { for (subpos, element) in more.0 { self.0.push((subpos + offset, element)); @@ -29,16 +32,9 @@ impl LayoutElements { } } -impl Default for LayoutElements { - fn default() -> Self { - Self::new() - } -} - -/// A layout element, which is the basic building block layouts are composed of. +/// A layout element, the basic building block layouts are composed of. #[derive(Debug, Clone, PartialEq)] pub enum LayoutElement { - /// Shaped text. Text(Shaped), } @@ -48,14 +44,17 @@ pub struct Shaped { pub text: String, pub face: FaceId, pub glyphs: Vec<GlyphId>, + /// The horizontal offsets of the glyphs with the same indices. Vertical + /// offets are not yet supported. pub offsets: Vec<f64>, + /// The font size. pub size: f64, } impl Shaped { - /// Create an empty shape run. - pub fn new(face: FaceId, size: f64) -> Shaped { - Shaped { + /// Create a new shape run with empty `text`, `glyphs` and `offsets`. + pub fn new(face: FaceId, size: f64) -> Self { + Self { text: String::new(), face, glyphs: vec![], @@ -65,12 +64,11 @@ impl Shaped { } /// Encode the glyph ids into a big-endian byte buffer. - pub fn encode_glyphs(&self) -> Vec<u8> { - const BYTES_PER_GLYPH: usize = 2; - let mut bytes = Vec::with_capacity(BYTES_PER_GLYPH * self.glyphs.len()); - for g in &self.glyphs { - bytes.push((g.0 >> 8) as u8); - bytes.push((g.0 & 0xff) as u8); + pub fn encode_glyphs_be(&self) -> Vec<u8> { + let mut bytes = Vec::with_capacity(2 * self.glyphs.len()); + for &GlyphId(g) in &self.glyphs { + bytes.push((g >> 8) as u8); + bytes.push((g & 0xff) as u8); } bytes } diff --git a/src/layout/line.rs b/src/layout/line.rs index 358d2ac9..069a4e56 100644 --- a/src/layout/line.rs +++ b/src/layout/line.rs @@ -1,70 +1,67 @@ -//! The line layouter arranges boxes into lines. +//! Arranging 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. +//! Along the primary axis, the boxes are laid out next to each other as long as +//! they fit into a line. When necessary, a line break is inserted and the new +//! line is offset along the secondary axis by the height of the previous line +//! plus extra line spacing. //! -//! Internally, the line layouter uses a stack layouter to arrange the finished -//! lines. +//! Internally, the line layouter uses a stack layouter to stack the finished +//! lines on top of each. -use super::stack::{StackLayouter, StackContext}; +use super::stack::{StackContext, StackLayouter}; use super::*; /// Performs the line layouting. -#[derive(Debug)] pub struct LineLayouter { - /// The context for layouting. ctx: LineContext, - /// The underlying stack layouter. stack: StackLayouter, - /// The currently written line. + /// The in-progress line. run: LineRun, } /// The context for line layouting. #[derive(Debug, Clone)] pub struct LineContext { - /// The spaces to layout in. + /// The spaces to layout into. pub spaces: LayoutSpaces, - /// The initial layouting axes, which can be updated by the - /// [`LineLayouter::set_axes`] method. + /// The initial layouting axes, which can be updated through `set_axes`. pub axes: LayoutAxes, - /// Which alignment to set on the resulting layout. This affects how it will - /// be positioned in a parent box. + /// The alignment of the _resulting_ layout. This does not effect the line + /// layouting itself, but rather how the finished layout will be positioned + /// in a parent layout. pub align: LayoutAlign, - /// Whether to have repeated spaces or to use only the first and only once. + /// Whether to spill over into copies of the last space or finish layouting + /// when the last space is used up. pub repeat: bool, - /// The line spacing. + /// The spacing to be inserted between each pair of lines. pub line_spacing: f64, } -/// 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)] +/// A sequence of boxes with the same alignment. A real line can consist of +/// multiple runs with different alignments. struct LineRun { - /// The so-far accumulated layouts in the line. + /// The so-far accumulated items of the run. layouts: Vec<(f64, BoxLayout)>, - /// The width and maximal height of the line. + /// The summed width and maximal height of the run. size: Size, /// 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. + /// When a new run is created the alignment is yet to be determined and + /// `None` as such. Once a layout is added, its alignment decides the + /// alignment for the whole run. align: Option<LayoutAlign>, - /// 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. + /// The amount of space left by another run on the same line or `None` if + /// this is the only run so far. usable: Option<f64>, - /// A possibly cached soft spacing or spacing state. + /// The spacing state. This influences how new spacing is handled, e.g. hard + /// spacing may override soft spacing. last_spacing: LastSpacing, } impl LineLayouter { /// Create a new line layouter. - pub fn new(ctx: LineContext) -> LineLayouter { - LineLayouter { + pub fn new(ctx: LineContext) -> Self { + Self { stack: StackLayouter::new(StackContext { spaces: ctx.spaces.clone(), axes: ctx.axes, @@ -76,14 +73,14 @@ impl LineLayouter { } } - /// Add a layout to the run. + /// Add a layout. pub fn add(&mut self, layout: BoxLayout) { let axes = self.ctx.axes; if let Some(align) = self.run.align { if layout.align.secondary != align.secondary { // TODO: Issue warning for non-fitting alignment in - // non-repeating context. + // non-repeating context. let fitting = self.stack.is_fitting_alignment(layout.align); if !fitting && self.ctx.repeat { self.finish_space(true); @@ -92,7 +89,6 @@ impl LineLayouter { } } else if layout.align.primary < align.primary { self.finish_line(); - } else if layout.align.primary > align.primary { let mut rest_run = LineRun::new(); @@ -137,25 +133,24 @@ impl LineLayouter { self.run.last_spacing = LastSpacing::None; } - /// Add multiple layouts to the run. + /// Add multiple layouts. /// - /// This function simply calls `add` repeatedly for each layout. + /// This is equivalent to calling `add` repeatedly for each layout. pub fn add_multiple(&mut self, layouts: MultiLayout) { for layout in layouts { self.add(layout); } } - /// The remaining usable size of the run. + /// The remaining usable size of the line. /// - /// This specifies how much more fits before a line break needs to be - /// issued. + /// This specifies how much more would fit before a line break would be + /// needed. fn usable(&self) -> Size { - // The base is the usable space per stack layouter. + // The base is the usable space of the 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 there was another run already, override the stack's size. if let Some(primary) = self.run.usable { usable.x = primary; } @@ -164,18 +159,17 @@ impl LineLayouter { usable } - /// Add spacing along the primary axis to the line. + /// Add spacing to the line. pub fn add_primary_spacing(&mut self, mut spacing: f64, kind: SpacingKind) { match kind { - // A hard space is simply an empty box. SpacingKind::Hard => { spacing = spacing.min(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. + // A soft space is cached since it might be consumed by a hard + // spacing. SpacingKind::Soft(level) => { let consumes = match self.run.last_spacing { LastSpacing::None => true, @@ -190,23 +184,23 @@ impl LineLayouter { } } - /// Finish the line and add secondary spacing to the underlying stack. + /// Finish the line and add spacing to the underlying stack. pub fn add_secondary_spacing(&mut self, spacing: f64, kind: SpacingKind) { self.finish_line_if_not_empty(); self.stack.add_spacing(spacing, kind) } - /// Update the layouting axes used by this layouter. + /// Update the layouting axes. pub fn set_axes(&mut self, axes: LayoutAxes) { self.finish_line_if_not_empty(); self.ctx.axes = axes; self.stack.set_axes(axes) } - /// Update the layouting spaces to use. + /// Update the layouting spaces. /// /// 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 + /// no boxes laid out into it yet. Otherwise, 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()); @@ -217,13 +211,11 @@ impl LineLayouter { self.ctx.line_spacing = line_spacing; } - /// 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. + /// The remaining inner spaces. If something is laid out into these spaces, + /// it will fit into this layouter's underlying stack. pub fn remaining(&self) -> LayoutSpaces { let mut spaces = self.stack.remaining(); - *spaces[0].size.secondary_mut(self.ctx.axes) - -= self.run.size.y; + *spaces[0].size.secondary_mut(self.ctx.axes) -= self.run.size.y; spaces } @@ -232,13 +224,13 @@ impl LineLayouter { self.run.size == Size::ZERO && self.run.layouts.is_empty() } - /// Finish the last line and compute the final list of boxes. + /// Finish everything up and return the final collection 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. + /// Finish the active space and start a new one. /// /// At the top level, this is a page break. pub fn finish_space(&mut self, hard: bool) { @@ -246,7 +238,7 @@ impl LineLayouter { self.stack.finish_space(hard) } - /// Finish the line and start a new one. + /// Finish the active line and start a new one. pub fn finish_line(&mut self) { let mut elements = LayoutElements::new(); @@ -265,9 +257,8 @@ impl LineLayouter { self.stack.add(BoxLayout { size: self.run.size.specialized(self.ctx.axes), - align: self.run.align - .unwrap_or(LayoutAlign::new(Start, Start)), - elements + align: self.run.align.unwrap_or(LayoutAlign::new(Start, Start)), + elements }); self.run = LineRun::new(); @@ -275,7 +266,6 @@ impl LineLayouter { self.stack.add_spacing(self.ctx.line_spacing, SpacingKind::LINE); } - /// Finish the current line if it is not empty. fn finish_line_if_not_empty(&mut self) { if !self.line_is_empty() { self.finish_line() @@ -284,8 +274,8 @@ impl LineLayouter { } impl LineRun { - fn new() -> LineRun { - LineRun { + fn new() -> Self { + Self { layouts: vec![], size: Size::ZERO, align: None, diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 143f1984..8a68a6a5 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,37 +1,37 @@ -//! Layouting types and engines. - -use async_trait::async_trait; - -use crate::Pass; -use crate::font::SharedFontLoader; -use crate::geom::{Size, Margins}; -use crate::style::{LayoutStyle, TextStyle, PageStyle}; -use crate::syntax::tree::SyntaxTree; - -use elements::LayoutElements; -use tree::TreeLayouter; -use prelude::*; +//! Layouting of syntax trees into box layouts. pub mod elements; pub mod line; pub mod primitive; pub mod stack; pub mod text; -pub mod tree; - -pub use primitive::*; +mod tree; /// Basic types used across the layouting engine. pub mod prelude { - pub use super::layout; pub use super::primitive::*; + pub use super::layout; pub use Dir::*; - pub use GenAxis::*; - pub use SpecAxis::*; pub use GenAlign::*; + pub use GenAxis::*; pub use SpecAlign::*; + pub use SpecAxis::*; } +pub use primitive::*; +pub use tree::layout_tree as layout; + +use async_trait::async_trait; + +use crate::font::SharedFontLoader; +use crate::geom::{Margins, Size}; +use crate::style::{LayoutStyle, PageStyle, TextStyle}; +use crate::syntax::tree::SyntaxTree; +use crate::Pass; + +use elements::LayoutElements; +use prelude::*; + /// A collection of layouts. pub type MultiLayout = Vec<BoxLayout>; @@ -40,47 +40,41 @@ pub type MultiLayout = Vec<BoxLayout>; pub struct BoxLayout { /// The size of the box. pub size: Size, - /// How to align this layout in a parent container. + /// How to align this box in a parent container. pub align: LayoutAlign, /// The elements composing this layout. pub elements: LayoutElements, } -/// Layouting of elements. +/// Comamnd-based layout. #[async_trait(?Send)] pub trait Layout { - /// Layout self into a sequence of layouting commands. - async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>>; -} - -/// Layout a syntax tree into a list of boxes. -pub async fn layout(tree: &SyntaxTree, ctx: LayoutContext<'_>) -> Pass<MultiLayout> { - let mut layouter = TreeLayouter::new(ctx); - layouter.layout_tree(tree).await; - layouter.finish() + /// Create a sequence of layouting commands to execute. + async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>>; } /// The context for layouting. #[derive(Debug, Clone)] pub struct LayoutContext<'a> { - /// The font loader to retrieve fonts from when typesetting text - /// using [`layout_text`]. + /// The font loader to query fonts from when typesetting text. pub loader: &'a SharedFontLoader, /// The style for pages and text. pub style: &'a LayoutStyle, - /// The base unpadded size of this container (for relative sizing). + /// The unpadded size of this container (the base 100% for relative sizes). pub base: Size, - /// The spaces to layout in. + /// The spaces to layout into. pub spaces: LayoutSpaces, - /// Whether to have repeated spaces or to use only the first and only once. + /// Whether to spill over into copies of the last space or finish layouting + /// when the last space is used up. pub repeat: bool, - /// The initial axes along which content is laid out. + /// The axes along which content is laid out. pub axes: LayoutAxes, - /// The alignment of the finished layout. + /// The alignment of the _resulting_ layout. This does not effect the line + /// layouting itself, but rather how the finished layout will be positioned + /// in a parent layout. pub align: LayoutAlign, - /// Whether the layout that is to be created will be nested in a parent - /// container. - pub nested: bool, + /// Whether this layouting process is the root page-building process. + pub root: bool, } /// A collection of layout spaces. @@ -89,17 +83,17 @@ pub type LayoutSpaces = Vec<LayoutSpace>; /// The space into which content is laid out. #[derive(Debug, Copy, Clone, PartialEq)] pub struct LayoutSpace { - /// The maximum size of the box to layout in. + /// The maximum size of the rectangle to layout into. pub size: Size, /// Padding that should be respected on each side. pub padding: Margins, /// Whether to expand the size of the resulting layout to the full size of - /// this space or to shrink them to fit the content. + /// this space or to shrink it to fit the content. pub expansion: LayoutExpansion, } impl LayoutSpace { - /// The offset from the origin to the start of content, that is, + /// The offset from the origin to the start of content, i.e. /// `(padding.left, padding.top)`. pub fn start(&self) -> Size { Size::new(self.padding.left, self.padding.top) @@ -110,9 +104,10 @@ impl LayoutSpace { self.size.unpadded(self.padding) } - /// A layout space without padding and size reduced by the padding. - pub fn usable_space(&self) -> LayoutSpace { - LayoutSpace { + /// The inner layout space with size reduced by the padding, zero padding of + /// its own and no layout expansion. + pub fn inner(&self) -> Self { + Self { size: self.usable(), padding: Margins::ZERO, expansion: LayoutExpansion::new(false, false), @@ -123,34 +118,33 @@ impl LayoutSpace { /// A sequence of layouting commands. pub type Commands<'a> = Vec<Command<'a>>; -/// Commands issued to the layouting engine by trees. +/// Commands executable by the layouting engine. #[derive(Debug, Clone)] pub enum Command<'a> { /// Layout the given tree in the current context (i.e. not nested). The /// content of the tree is not laid out into a separate box and then added, - /// but simply laid out flat in the active layouting process. + /// but simply laid out flatly 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. + /// text while keeping it part of the current paragraph. LayoutSyntaxTree(&'a SyntaxTree), - /// Add a already computed layout. + /// Add a finished layout. Add(BoxLayout), /// Add multiple layouts, one after another. This is equivalent to multiple - /// [Add](Command::Add) commands. + /// `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. + /// Add spacing of the given kind along the primary or secondary axis. The + /// kind defines how the spacing interacts with surrounding spacing. AddSpacing(f64, SpacingKind, GenAxis), /// Start a new line. BreakLine, /// Start a new paragraph. BreakParagraph, - /// Start a new page, which will exist in the finished layout even if it + /// Start a new page, which will be part of the finished layout even if it /// stays empty (since the page break is a _hard_ space break). BreakPage, @@ -162,6 +156,6 @@ pub enum Command<'a> { /// Update the alignment for future boxes added to this layouting process. SetAlignment(LayoutAlign), /// Update the layouting axes along which future boxes will be laid - /// out. This finishes the current line. + /// out. This ends the current line. SetAxes(LayoutAxes), } diff --git a/src/layout/primitive.rs b/src/layout/primitive.rs index 2eb5669b..1d79f530 100644 --- a/src/layout/primitive.rs +++ b/src/layout/primitive.rs @@ -1,29 +1,27 @@ //! Layouting primitives. use std::fmt::{self, Display, Formatter}; + use super::prelude::*; -/// Specifies along which axes content is laid out. +/// Specifies the axes along content is laid out. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct LayoutAxes { - /// The primary layouting direction. pub primary: Dir, - /// The secondary layouting direction. pub secondary: Dir, } impl LayoutAxes { - /// Create a new instance from the two values. + /// Create a new instance from the two directions. /// /// # Panics - /// This function panics if the axes are aligned, that is, they are + /// This function panics if the directions are aligned, i.e. if they are /// on the same axis. - pub fn new(primary: Dir, secondary: Dir) -> LayoutAxes { + pub fn new(primary: Dir, secondary: Dir) -> Self { if primary.axis() == secondary.axis() { - panic!("invalid aligned axes {} and {}", primary, secondary); + panic!("directions {} and {} are aligned", primary, secondary); } - - LayoutAxes { primary, secondary } + Self { primary, secondary } } /// Return the direction of the specified generic axis. @@ -46,9 +44,13 @@ impl LayoutAxes { /// Directions along which content is laid out. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Dir { - LTT, + /// Left to right. + LTR, + /// Right to left. RTL, + /// Top to bottom. TTB, + /// Bottom to top. BTT, } @@ -56,34 +58,34 @@ impl Dir { /// The specific axis this direction belongs to. pub fn axis(self) -> SpecAxis { match self { - LTT | RTL => Horizontal, + LTR | RTL => Horizontal, TTB | BTT => Vertical, } } - /// Whether this axis points into the positive coordinate direction. + /// Whether this direction points into the positive coordinate direction. /// - /// The positive axes are left-to-right and top-to-bottom. + /// The positive directions are left-to-right and top-to-bottom. pub fn is_positive(self) -> bool { match self { - LTT | TTB => true, + LTR | TTB => true, RTL | BTT => false, } } /// The factor for this direction. /// - /// - `1` if the direction is positive. - /// - `-1` if the direction is negative. + /// - `1.0` if the direction is positive. + /// - `-1.0` if the direction is negative. pub fn factor(self) -> f64 { if self.is_positive() { 1.0 } else { -1.0 } } - /// The inverse axis. - pub fn inv(self) -> Dir { + /// The inverse direction. + pub fn inv(self) -> Self { match self { - LTT => RTL, - RTL => LTT, + LTR => RTL, + RTL => LTR, TTB => BTT, BTT => TTB, } @@ -93,7 +95,7 @@ impl Dir { impl Display for Dir { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.pad(match self { - LTT => "ltr", + LTR => "ltr", RTL => "rtl", TTB => "ttb", BTT => "btt", @@ -104,9 +106,9 @@ impl Display for Dir { /// The two generic layouting axes. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum GenAxis { - /// The primary axis along which words are laid out. + /// The primary layouting direction along which text and lines flow. Primary, - /// The secondary axis along which lines and paragraphs are laid out. + /// The secondary layouting direction along which paragraphs grow. Secondary, } @@ -154,19 +156,17 @@ impl Display for SpecAxis { /// Specifies where to align a layout in a parent container. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct LayoutAlign { - /// The alignment along the primary axis. pub primary: GenAlign, - /// The alignment along the secondary axis. pub secondary: GenAlign, } impl LayoutAlign { - /// Create a new instance from the two values. - pub fn new(primary: GenAlign, secondary: GenAlign) -> LayoutAlign { - LayoutAlign { primary, secondary } + /// Create a new instance from the two alignments. + pub fn new(primary: GenAlign, secondary: GenAlign) -> Self { + Self { primary, secondary } } - /// Return the alignment of the specified generic axis. + /// Return the alignment for the specified generic axis. pub fn get(self, axis: GenAxis) -> GenAlign { match axis { Primary => self.primary, @@ -174,7 +174,7 @@ impl LayoutAlign { } } - /// Borrow the alignment of the specified generic axis mutably. + /// Borrow the alignment for the specified generic axis mutably. pub fn get_mut(&mut self, axis: GenAxis) -> &mut GenAlign { match axis { Primary => &mut self.primary, @@ -183,7 +183,7 @@ impl LayoutAlign { } } -/// Where to align content along a generic context. +/// Where to align content along an axis in a generic context. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum GenAlign { Start, @@ -193,7 +193,7 @@ pub enum GenAlign { impl GenAlign { /// The inverse alignment. - pub fn inv(self) -> GenAlign { + pub fn inv(self) -> Self { match self { Start => End, Center => Center, @@ -212,7 +212,7 @@ impl Display for GenAlign { } } -/// Where to align content in a specific context. +/// Where to align content along an axis in a specific context. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum SpecAlign { Left, @@ -225,7 +225,7 @@ pub enum SpecAlign { impl SpecAlign { /// The specific axis this alignment refers to. /// - /// Returns `None` if this is center. + /// Returns `None` if this is `Center` since the axis is unknown. pub fn axis(self) -> Option<SpecAxis> { match self { Self::Left => Some(Horizontal), @@ -236,7 +236,7 @@ impl SpecAlign { } } - /// Convert this to a generic alignment. + /// The generic version of this alignment in the given system of axes. pub fn to_generic(self, axes: LayoutAxes) -> GenAlign { let get = |spec: SpecAxis, align: GenAlign| { let axis = spec.to_generic(axes); @@ -277,8 +277,8 @@ pub struct LayoutExpansion { impl LayoutExpansion { /// Create a new instance from the two values. - pub fn new(horizontal: bool, vertical: bool) -> LayoutExpansion { - LayoutExpansion { horizontal, vertical } + pub fn new(horizontal: bool, vertical: bool) -> Self { + Self { horizontal, vertical } } /// Return the expansion value for the given specific axis. @@ -298,8 +298,7 @@ impl LayoutExpansion { } } -/// Defines how a given spacing interacts with (possibly existing) surrounding -/// spacing. +/// Defines how spacing interacts with 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 @@ -317,31 +316,31 @@ pub enum SpacingKind { impl SpacingKind { /// The standard spacing kind used for paragraph spacing. - pub const PARAGRAPH: SpacingKind = SpacingKind::Soft(1); + pub const PARAGRAPH: Self = Self::Soft(1); /// The standard spacing kind used for line spacing. - pub const LINE: SpacingKind = SpacingKind::Soft(2); + pub const LINE: Self = Self::Soft(2); /// The standard spacing kind used for word spacing. - pub const WORD: SpacingKind = SpacingKind::Soft(1); + pub const WORD: Self = Self::Soft(1); } /// 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`. +/// +/// Since the last inserted item may not be spacing at all, this can be `None`. #[derive(Debug, Copy, Clone, PartialEq)] pub(crate) enum LastSpacing { /// The last item was hard spacing. Hard, /// The last item was soft spacing with the given width and level. Soft(f64, u32), - /// The last item was not spacing. + /// The last item wasn't spacing. None, } impl LastSpacing { /// The width of the soft space if this is a soft space or zero otherwise. - pub(crate) fn soft_or_zero(self) -> f64 { + pub fn soft_or_zero(self) -> f64 { match self { LastSpacing::Soft(space, _) => space, _ => 0.0, diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 28da74b7..62f2c976 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -1,11 +1,9 @@ -//! The stack layouter arranges boxes along the secondary layouting axis. +//! Arranging boxes into a stack along the secondary 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: +//! Individual layouts can be aligned at `Start`, `Center` or `End` along both +//! axes. These alignments are with respect to the size of the finished layout +//! and not the total usable size. This means that a later layout can have +//! influence on the position of an earlier one. Consider the following example. //! ```typst //! [align: right][A word.] //! [align: left][A sentence with a couple more words.] @@ -25,38 +23,35 @@ use crate::geom::Value4; use super::*; /// Performs the stack layouting. -#[derive(Debug)] pub struct StackLayouter { - /// The context for layouting. ctx: StackContext, - /// The output layouts. layouts: MultiLayout, - /// The currently active layout space. + /// The in-progress space. space: Space, } /// The context for stack layouting. #[derive(Debug, Clone)] pub struct StackContext { - /// The spaces to layout in. + /// The spaces to layout into. pub spaces: LayoutSpaces, - /// The initial layouting axes, which can be updated by the - /// [`StackLayouter::set_axes`] method. + /// The initial layouting axes, which can be updated through `set_axes`. pub axes: LayoutAxes, - /// Which alignment to set on the resulting layout. This affects how it will - /// be positioned in a parent box. + /// The alignment of the _resulting_ layout. This does not effect the line + /// layouting itself, but rather how the finished layout will be positioned + /// in a parent layout. pub align: LayoutAlign, - /// Whether to have repeated spaces or to use only the first and only once. + /// Whether to spill over into copies of the last space or finish layouting + /// when the last space is used up. pub repeat: bool, } /// A layout space composed of subspaces which can have different axes and /// alignments. -#[derive(Debug)] struct Space { - /// The index of this space in the list of spaces. + /// The index of this space in `ctx.spaces`. index: usize, - /// Whether to add the layout for this space even if it would be empty. + /// Whether to include a layout for this space even if it would be empty. hard: bool, /// The so-far accumulated layouts. layouts: Vec<(LayoutAxes, BoxLayout)>, @@ -66,18 +61,20 @@ struct Space { usable: Size, /// The specialized extra-needed size to affect the size at all. extra: Size, - /// The rulers of a space dictate which alignments for new boxes are still - /// allowed and which require a new space to be started. + /// Dictate which alignments for new boxes are still allowed and which + /// require a new space to be started. For example, after an `End`-aligned + /// item, no `Start`-aligned one can follow. rulers: Value4<GenAlign>, - /// The last added spacing if the last added thing was spacing. + /// The spacing state. This influences how new spacing is handled, e.g. hard + /// spacing may override soft spacing. last_spacing: LastSpacing, } impl StackLayouter { /// Create a new stack layouter. - pub fn new(ctx: StackContext) -> StackLayouter { + pub fn new(ctx: StackContext) -> Self { let space = ctx.spaces[0]; - StackLayouter { + Self { ctx, layouts: MultiLayout::new(), space: Space::new(0, true, space.usable()), @@ -87,8 +84,8 @@ impl StackLayouter { /// Add a layout to the stack. pub fn add(&mut self, layout: BoxLayout) { // If the alignment cannot be fitted in this space, finish it. - // TODO: Issue warning for non-fitting alignment in - // non-repeating context. + // TODO: Issue warning for non-fitting alignment in non-repeating + // context. if !self.update_rulers(layout.align) && self.ctx.repeat { self.finish_space(true); } @@ -116,14 +113,14 @@ impl StackLayouter { /// Add multiple layouts to the stack. /// - /// This function simply calls `add` repeatedly for each layout. + /// This is equivalent to calling `add` repeatedly for each layout. pub fn add_multiple(&mut self, layouts: MultiLayout) { for layout in layouts { self.add(layout); } } - /// Add secondary spacing to the stack. + /// Add spacing to the stack. pub fn add_spacing(&mut self, mut spacing: f64, kind: SpacingKind) { match kind { // A hard space is simply an empty box. @@ -133,11 +130,14 @@ impl StackLayouter { let size = Size::with_y(spacing); self.update_metrics(size); - self.space.layouts.push((self.ctx.axes, BoxLayout { - size: size.specialized(self.ctx.axes), - align: LayoutAlign::new(Start, Start), - elements: LayoutElements::new(), - })); + self.space.layouts.push(( + self.ctx.axes, + BoxLayout { + size: size.specialized(self.ctx.axes), + align: LayoutAlign::new(Start, Start), + elements: LayoutElements::new(), + } + )); self.space.last_spacing = LastSpacing::Hard; } @@ -158,8 +158,6 @@ impl StackLayouter { } } - /// Update the size metrics to reflect that a layout or spacing with the - /// given generalized size has been added. fn update_metrics(&mut self, added: Size) { let axes = self.ctx.axes; @@ -177,31 +175,29 @@ impl StackLayouter { *self.space.usable.secondary_mut(axes) -= added.y; } - /// Update the rulers to account for the new layout. Returns true if a - /// space break is necessary. + /// Returns true if a space break is necessary. fn update_rulers(&mut self, align: LayoutAlign) -> bool { let allowed = self.is_fitting_alignment(align); if allowed { - *self.space.rulers.get_mut(self.ctx.axes.secondary, Start) - = align.secondary; + *self.space.rulers.get_mut(self.ctx.axes.secondary, Start) = + align.secondary; } allowed } - /// Whether a layout with the given alignment can still be layouted in the - /// active space. - pub fn is_fitting_alignment(&mut self, align: LayoutAlign) -> bool { + /// Whether a layout with the given alignment can still be layouted into the + /// active space or a space break is necessary. + pub(crate) fn is_fitting_alignment(&mut self, align: LayoutAlign) -> bool { self.is_fitting_axis(self.ctx.axes.primary, align.primary) && self.is_fitting_axis(self.ctx.axes.secondary, align.secondary) } - /// Whether the given alignment is still allowed according to the rulers. fn is_fitting_axis(&mut self, dir: Dir, align: GenAlign) -> bool { align >= *self.space.rulers.get_mut(dir, Start) - && align <= self.space.rulers.get_mut(dir, End).inv() + && align <= self.space.rulers.get_mut(dir, End).inv() } - /// Change the layouting axes used by this layouter. + /// Update the layouting axes. pub fn set_axes(&mut self, axes: LayoutAxes) { // Forget the spacing because it is not relevant anymore. if axes.secondary != self.ctx.axes.secondary { @@ -211,10 +207,10 @@ impl StackLayouter { self.ctx.axes = axes; } - /// Change the layouting spaces to use. + /// Update the layouting spaces. /// /// 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 + /// no boxes laid out into it yet. Otherwise, the followup spaces are /// replaced. pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) { if replace_empty && self.space_is_empty() { @@ -234,13 +230,13 @@ impl StackLayouter { if space.usable().fits(size) { self.finish_space(true); self.start_space(start + index, true); - return; + break; } } } - /// The remaining unpadded, unexpanding spaces. If a function is laid out - /// into these spaces, it will fit into this stack. + /// The remaining inner spaces. If something is laid out into these spaces, + /// it will fit into this stack. pub fn remaining(&self) -> LayoutSpaces { let size = self.usable(); @@ -251,7 +247,7 @@ impl StackLayouter { }]; for space in &self.ctx.spaces[self.next_space()..] { - spaces.push(space.usable_space()); + spaces.push(space.inner()); } spaces @@ -264,17 +260,17 @@ impl StackLayouter { .specialized(self.ctx.axes) } - /// Whether the current layout space (not subspace) is empty. + /// Whether the current layout space is empty. pub fn space_is_empty(&self) -> bool { self.space.size == Size::ZERO && self.space.layouts.is_empty() } - /// Whether the current layout space is the last is the followup list. + /// Whether the current layout space is the last in the followup list. pub fn space_is_last(&self) -> bool { self.space.index == self.ctx.spaces.len() - 1 } - /// Compute the finished list of boxes. + /// Finish everything up and return the final collection of boxes. pub fn finish(mut self) -> MultiLayout { if self.space.hard || !self.space_is_empty() { self.finish_space(false); @@ -282,7 +278,7 @@ impl StackLayouter { self.layouts } - /// Finish the current space and start a new one. + /// Finish active current space and start a new one. pub fn finish_space(&mut self, hard: bool) { let space = self.ctx.spaces[self.space.index]; @@ -322,8 +318,8 @@ impl StackLayouter { // layout uses up space from the origin to the end. Thus, it reduces // the usable space for following layouts at it's origin by its // extent along the secondary axis. - *bound.get_mut(axes.secondary, Start) - += axes.secondary.factor() * layout.size.secondary(*axes); + *bound.get_mut(axes.secondary, Start) += + axes.secondary.factor() * layout.size.secondary(*axes); } // ------------------------------------------------------------------ // @@ -351,8 +347,8 @@ impl StackLayouter { // We reduce the bounding box of this layout at it's end by the // accumulated secondary extent of all layouts we have seen so far, // which are the layouts after this one since we iterate reversed. - *bound.get_mut(axes.secondary, End) - -= axes.secondary.factor() * extent.y; + *bound.get_mut(axes.secondary, End) -= + axes.secondary.factor() * extent.y; // Then, we add this layout's secondary extent to the accumulator. let size = layout.size.generalized(*axes); @@ -395,21 +391,19 @@ impl StackLayouter { self.start_space(self.next_space(), hard) } - /// Start a new space with the given index. fn start_space(&mut self, index: usize, hard: bool) { let space = self.ctx.spaces[index]; self.space = Space::new(index, hard, space.usable()); } - /// The index of the next space. fn next_space(&self) -> usize { (self.space.index + 1).min(self.ctx.spaces.len() - 1) } } impl Space { - fn new(index: usize, hard: bool, usable: Size) -> Space { - Space { + fn new(index: usize, hard: bool, usable: Size) -> Self { + Self { index, hard, layouts: vec![], diff --git a/src/layout/text.rs b/src/layout/text.rs index 5c18cd32..b95110b7 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -1,17 +1,23 @@ -//! The text layouter layouts continous pieces of text into boxes. +//! Layouting of 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 ttf_parser::GlyphId; use fontdock::{FaceId, FaceQuery, FontStyle}; +use ttf_parser::GlyphId; + use crate::font::SharedFontLoader; use crate::geom::Size; use crate::style::TextStyle; use super::elements::{LayoutElement, Shaped}; use super::*; +/// Layouts text into a box. +pub async fn layout_text(text: &str, ctx: TextContext<'_>) -> BoxLayout { + TextLayouter::new(text, ctx).layout().await +} + /// Performs the text layouting. #[derive(Debug)] struct TextLayouter<'a> { @@ -26,28 +32,24 @@ struct TextLayouter<'a> { /// The context for text layouting. #[derive(Debug, Copy, Clone)] pub struct TextContext<'a> { - /// The font loader to retrieve fonts from when typesetting text - /// using [`layout_text`]. + /// The font loader to retrieve fonts from when typesetting text with + /// `layout_text`. pub loader: &'a SharedFontLoader, /// 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. + /// The direction into which the word is laid out. For now, only horizontal + /// directions are supported. + pub dir: Dir, + /// The alignment of the _resulting_ layout. This does not effect the line + /// layouting itself, but rather how the finished layout will be positioned + /// in a parent layout. pub align: LayoutAlign, } -/// Layouts text into a box. -pub async fn layout_text(text: &str, ctx: TextContext<'_>) -> BoxLayout { - TextLayouter::new(text, ctx).layout().await -} - impl<'a> TextLayouter<'a> { - /// Create a new text layouter. - fn new(text: &'a str, ctx: TextContext<'a>) -> TextLayouter<'a> { - TextLayouter { + fn new(text: &'a str, ctx: TextContext<'a>) -> Self { + Self { ctx, text, shaped: Shaped::new(FaceId::MAX, ctx.style.font_size()), @@ -57,10 +59,9 @@ impl<'a> TextLayouter<'a> { } } - /// Do the layouting. async fn layout(mut self) -> BoxLayout { // If the primary axis is negative, we layout the characters reversed. - if self.ctx.axes.primary.is_positive() { + if self.ctx.dir.is_positive() { for c in self.text.chars() { self.layout_char(c).await; } @@ -83,7 +84,6 @@ impl<'a> TextLayouter<'a> { } } - /// Layout an individual character. async fn layout_char(&mut self, c: char) { let (index, glyph, char_width) = match self.select_font(c).await { Some(selected) => selected, @@ -115,11 +115,8 @@ impl<'a> TextLayouter<'a> { self.width += char_width; } - /// Select the best font for a character and return its index along with - /// the width of the char in the font. async fn select_font(&mut self, c: char) -> Option<(FaceId, GlyphId, f64)> { let mut loader = self.ctx.loader.borrow_mut(); - let mut variant = self.ctx.style.variant; if self.ctx.style.bolder { diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 44c59211..d20fc666 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -1,19 +1,26 @@ -//! The tree layouter layouts trees (i.e. -//! [syntax trees](crate::syntax::SyntaxTree) and [functions](crate::func)) -//! by executing commands issued by the trees. +//! Layouting of syntax trees. -use crate::{Pass, Feedback, DynFuture}; use crate::style::LayoutStyle; use crate::syntax::decoration::Decoration; -use crate::syntax::tree::{SyntaxTree, SyntaxNode, DynamicNode}; use crate::syntax::span::{Span, Spanned}; -use super::line::{LineLayouter, LineContext}; +use crate::syntax::tree::{DynamicNode, SyntaxNode, SyntaxTree}; +use crate::{DynFuture, Feedback, Pass}; +use super::line::{LineContext, LineLayouter}; use super::text::{layout_text, TextContext}; use super::*; +/// Layout a syntax tree into a collection of boxes. +pub async fn layout_tree( + tree: &SyntaxTree, + ctx: LayoutContext<'_>, +) -> Pass<MultiLayout> { + let mut layouter = TreeLayouter::new(ctx); + layouter.layout_tree(tree).await; + layouter.finish() +} + /// Performs the tree layouting. -#[derive(Debug)] -pub struct TreeLayouter<'a> { +struct TreeLayouter<'a> { ctx: LayoutContext<'a>, layouter: LineLayouter, style: LayoutStyle, @@ -21,9 +28,8 @@ pub struct TreeLayouter<'a> { } impl<'a> TreeLayouter<'a> { - /// Create a new tree layouter. - pub fn new(ctx: LayoutContext<'a>) -> TreeLayouter<'a> { - TreeLayouter { + fn new(ctx: LayoutContext<'a>) -> Self { + Self { layouter: LineLayouter::new(LineContext { spaces: ctx.spaces.clone(), axes: ctx.axes, @@ -37,15 +43,17 @@ impl<'a> TreeLayouter<'a> { } } - /// Layout a syntax tree by directly processing the nodes instead of using - /// the command based architecture. - pub async fn layout_tree(&mut self, tree: &SyntaxTree) { + async fn layout_tree(&mut self, tree: &SyntaxTree) { for node in tree { self.layout_node(node).await; } } - pub async fn layout_node(&mut self, node: &Spanned<SyntaxNode>) { + fn finish(self) -> Pass<MultiLayout> { + Pass::new(self.layouter.finish(), self.feedback) + } + + async fn layout_node(&mut self, node: &Spanned<SyntaxNode>) { let decorate = |this: &mut TreeLayouter, deco| { this.feedback.decorations.push(Spanned::new(deco, node.span)); }; @@ -80,7 +88,10 @@ impl<'a> TreeLayouter<'a> { SyntaxNode::Raw(lines) => { // TODO: Make this more efficient. let fallback = self.style.text.fallback.clone(); - self.style.text.fallback.list_mut().insert(0, "monospace".to_string()); + self.style.text.fallback + .list_mut() + .insert(0, "monospace".to_string()); + self.style.text.fallback.flatten(); // Layout the first line. @@ -104,17 +115,15 @@ impl<'a> TreeLayouter<'a> { } } - /// Layout a node into this layouting process. - pub async fn layout_dyn(&mut self, dynamic: Spanned<&dyn DynamicNode>) { - // Execute the tree's layout function which generates the commands. + async fn layout_dyn(&mut self, dynamic: Spanned<&dyn DynamicNode>) { + // Execute the tree's command-generating layout function. let layouted = dynamic.v.layout(LayoutContext { style: &self.style, spaces: self.layouter.remaining(), - nested: true, - .. self.ctx + root: true, + ..self.ctx }).await; - // Add the errors generated by the tree to the error list. self.feedback.extend_offset(layouted.feedback, dynamic.span.start); for command in layouted.output { @@ -122,13 +131,6 @@ impl<'a> TreeLayouter<'a> { } } - /// Compute the finished list of boxes. - pub fn finish(self) -> Pass<MultiLayout> { - Pass::new(self.layouter.finish(), self.feedback) - } - - /// Execute a command issued by a tree. When the command is errorful, the - /// given span is stored with the error. fn execute_command<'r>( &'r mut self, command: Command<'r>, @@ -149,13 +151,13 @@ impl<'a> TreeLayouter<'a> { BreakLine => self.layouter.finish_line(), BreakParagraph => self.layout_paragraph(), BreakPage => { - if self.ctx.nested { + if self.ctx.root { + self.layouter.finish_space(true) + } else { error!( @self.feedback, tree_span, - "page break cannot be issued from nested context", + "page break cannot only be issued from root context", ); - } else { - self.layouter.finish_space(true) } } @@ -164,12 +166,7 @@ impl<'a> TreeLayouter<'a> { self.style.text = style; } SetPageStyle(style) => { - if self.ctx.nested { - error!( - @self.feedback, tree_span, - "page style cannot be changed from nested context", - ); - } else { + if self.ctx.root { self.style.page = style; // The line layouter has no idea of page styles and thus we @@ -184,6 +181,11 @@ impl<'a> TreeLayouter<'a> { expansion: LayoutExpansion::new(true, true), } ], true); + } else { + error!( + @self.feedback, tree_span, + "page style cannot only be changed from root context", + ); } } @@ -195,17 +197,20 @@ impl<'a> TreeLayouter<'a> { } }) } - /// 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, - style: &self.style.text, - axes: self.ctx.axes, - align: self.ctx.align, - }).await) + self.layouter.add( + layout_text( + text, + TextContext { + loader: &self.ctx.loader, + style: &self.style.text, + dir: self.ctx.axes.primary, + align: self.ctx.align, + } + ).await + ); } - /// Add the spacing for a syntactic space node. fn layout_space(&mut self) { self.layouter.add_primary_spacing( self.style.text.word_spacing(), @@ -213,7 +218,6 @@ impl<'a> TreeLayouter<'a> { ); } - /// Finish the paragraph and add paragraph spacing. fn layout_paragraph(&mut self) { self.layouter.add_secondary_spacing( self.style.text.paragraph_spacing(), |
