summaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-01-19 21:50:20 +0100
committerLaurenz <laurmaedje@gmail.com>2020-01-19 21:53:24 +0100
commit95e6b078fecddeaa3d6f2c920b617201b74bf01e (patch)
tree1c03b0b16d614a5a2350dccf71a1eb1e34f9a812 /src/layout
parent277f2d2176f5e98305870f90b16af3feae1bb3d1 (diff)
Move to non-fatal errors 🪂 [WIP]
- Dynamic models instead of SyntaxTrees - No more ParseResult/LayoutResult - Errors and Decorations which are propagated to parent contexts - Models are finally clonable
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/line.rs72
-rw-r--r--src/layout/mod.rs42
-rw-r--r--src/layout/model.rs193
-rw-r--r--src/layout/stack.rs55
-rw-r--r--src/layout/text.rs59
-rw-r--r--src/layout/tree.rs163
6 files changed, 310 insertions, 274 deletions
diff --git a/src/layout/line.rs b/src/layout/line.rs
index c4205e81..f7777ae0 100644
--- a/src/layout/line.rs
+++ b/src/layout/line.rs
@@ -67,18 +67,21 @@ impl LineLayouter {
}
/// Add a layout to the run.
- pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
+ pub fn add(&mut self, layout: Layout) {
let axes = self.ctx.axes;
if let Some(alignment) = self.run.alignment {
if layout.alignment.secondary != alignment.secondary {
- if self.stack.is_fitting_alignment(layout.alignment) {
- self.finish_line()?;
+ // TODO: Issue warning for non-fitting alignment in
+ // non-repeating context.
+ let fitting = self.stack.is_fitting_alignment(layout.alignment);
+ if !fitting && self.ctx.repeat {
+ self.finish_space(true);
} else {
- self.finish_space(true)?;
+ self.finish_line();
}
} else if layout.alignment.primary < alignment.primary {
- self.finish_line()?;
+ self.finish_line();
} else if layout.alignment.primary > alignment.primary {
let mut rest_run = LineRun::new();
@@ -92,7 +95,7 @@ impl LineLayouter {
rest_run.size.y = self.run.size.y;
- self.finish_line()?;
+ self.finish_line();
self.stack.add_spacing(-rest_run.size.y, SpacingKind::Hard);
self.run = rest_run;
@@ -105,16 +108,14 @@ impl LineLayouter {
let size = layout.dimensions.generalized(axes);
- while !self.usable().fits(size) {
+ if !self.usable().fits(size) {
if !self.line_is_empty() {
- self.finish_line()?;
- } else {
- if self.stack.space_is_last() && self.stack.space_is_empty() {
- error!("cannot fit box of size {} into usable size of {}",
- layout.dimensions, self.usable());
- }
+ self.finish_line();
+ }
- self.finish_space(true)?;
+ // TODO: Issue warning about overflow if there is overflow.
+ if !self.usable().fits(size) {
+ self.stack.skip_to_fitting_space(layout.dimensions);
}
}
@@ -124,18 +125,15 @@ impl LineLayouter {
self.run.size.x += size.x;
self.run.size.y.max_eq(size.y);
self.run.last_spacing = LastSpacing::None;
-
- Ok(())
}
/// Add multiple layouts to the run.
///
/// This function simply calls `add` repeatedly for each layout.
- pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
+ pub fn add_multiple(&mut self, layouts: MultiLayout) {
for layout in layouts {
- self.add(layout)?;
+ self.add(layout);
}
- Ok(())
}
/// The remaining usable size in the run.
@@ -180,20 +178,16 @@ impl LineLayouter {
}
/// Finish the run and add secondary spacing to the underlying stack.
- pub fn add_secondary_spacing(
- &mut self,
- spacing: Size,
- kind: SpacingKind
- ) -> LayoutResult<()> {
- self.finish_line_if_not_empty()?;
- Ok(self.stack.add_spacing(spacing, kind))
+ 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.
- pub fn set_axes(&mut self, axes: LayoutAxes) -> LayoutResult<()> {
- self.finish_line_if_not_empty()?;
+ pub fn set_axes(&mut self, axes: LayoutAxes) {
+ self.finish_line_if_not_empty();
self.ctx.axes = axes;
- Ok(self.stack.set_axes(axes))
+ self.stack.set_axes(axes)
}
/// Change the layouting spaces to use.
@@ -224,19 +218,19 @@ impl LineLayouter {
}
/// Finish the last line and compute the final multi-layout.
- pub fn finish(mut self) -> LayoutResult<MultiLayout> {
- self.finish_line_if_not_empty()?;
+ 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.
- pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> {
- self.finish_line_if_not_empty()?;
+ 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.
- pub fn finish_line(&mut self) -> LayoutResult<()> {
+ pub fn finish_line(&mut self) {
let mut actions = LayoutActions::new();
let layouts = std::mem::replace(&mut self.run.layouts, vec![]);
@@ -257,21 +251,17 @@ impl LineLayouter {
alignment: self.run.alignment
.unwrap_or(LayoutAlignment::new(Origin, Origin)),
actions: actions.to_vec(),
- })?;
+ });
self.run = LineRun::new();
- self.stack.add_spacing(self.ctx.line_spacing, LINE_KIND);
-
- Ok(())
+ 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) -> LayoutResult<()> {
+ fn finish_line_if_not_empty(&mut self) {
if !self.line_is_empty() {
self.finish_line()
- } else {
- Ok(())
}
}
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 49551945..75d34409 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -4,23 +4,20 @@ use std::io::{self, Write};
use smallvec::SmallVec;
use toddle::query::{SharedFontLoader, FontIndex};
+use crate::error::Error;
+use crate::syntax::SpanVec;
use crate::size::{Size, Size2D, SizeBox};
use crate::style::LayoutStyle;
mod actions;
-mod tree;
+mod model;
mod line;
mod stack;
mod text;
/// Common types for layouting.
pub mod prelude {
- pub use super::{
- layout, LayoutResult,
- MultiLayout, Layout, LayoutContext, LayoutSpaces, LayoutSpace,
- LayoutExpansion, LayoutAxes, GenericAxis, SpecificAxis, Direction,
- LayoutAlignment, Alignment, SpacingKind,
- };
+ pub use super::*;
pub use GenericAxis::*;
pub use SpecificAxis::*;
pub use Direction::*;
@@ -29,7 +26,7 @@ pub mod prelude {
/// Different kinds of layouters (fully re-exported).
pub mod layouters {
- pub use super::tree::layout;
+ pub use super::model::layout;
pub use super::line::{LineLayouter, LineContext};
pub use super::stack::{StackLayouter, StackContext};
pub use super::text::{layout_text, TextContext};
@@ -40,8 +37,19 @@ pub use self::layouters::*;
pub use self::prelude::*;
-/// The result type for layouting.
-pub type LayoutResult<T> = crate::TypesetResult<T>;
+pub struct Layouted<T> {
+ pub output: T,
+ pub errors: SpanVec<Error>,
+}
+
+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 collection of layouts.
pub type MultiLayout = Vec<Layout>;
@@ -361,14 +369,16 @@ pub enum SpacingKind {
Soft(u32),
}
-/// The standard spacing kind used for paragraph spacing.
-const PARAGRAPH_KIND: SpacingKind = SpacingKind::Soft(1);
+impl SpacingKind {
+ /// The standard spacing kind used for paragraph spacing.
+ pub const PARAGRAPH: SpacingKind = SpacingKind::Soft(1);
-/// The standard spacing kind used for line spacing.
-const LINE_KIND: SpacingKind = SpacingKind::Soft(2);
+ /// The standard spacing kind used for line spacing.
+ pub const LINE: SpacingKind = SpacingKind::Soft(2);
-/// The standard spacing kind used for word spacing.
-const WORD_KIND: SpacingKind = SpacingKind::Soft(1);
+ /// The standard spacing kind used for word spacing.
+ pub const WORD: SpacingKind = SpacingKind::Soft(1);
+}
/// The last appeared spacing.
#[derive(Debug, Copy, Clone, PartialEq)]
diff --git a/src/layout/model.rs b/src/layout/model.rs
new file mode 100644
index 00000000..bcec5ceb
--- /dev/null
+++ b/src/layout/model.rs
@@ -0,0 +1,193 @@
+use std::pin::Pin;
+use std::future::Future;
+use smallvec::smallvec;
+
+use crate::error::Error;
+use crate::func::Command;
+use crate::syntax::{Model, DynFuture, SyntaxModel, Node};
+use crate::syntax::{SpanVec, Spanned, Span, offset_spans};
+use super::*;
+
+
+pub async fn layout(
+ model: &SyntaxModel,
+ ctx: LayoutContext<'_, '_>
+) -> Layouted<MultiLayout> {
+ let mut layouter = ModelLayouter::new(ctx);
+ layouter.layout_syntax_model(model).await;
+ layouter.finish()
+}
+
+#[derive(Debug, Clone)]
+struct ModelLayouter<'a, 'p> {
+ ctx: LayoutContext<'a, 'p>,
+ layouter: LineLayouter,
+ style: LayoutStyle,
+ errors: SpanVec<Error>,
+}
+
+impl<'a, 'p> ModelLayouter<'a, 'p> {
+ /// Create a new syntax tree layouter.
+ fn new(ctx: LayoutContext<'a, 'p>) -> ModelLayouter<'a, 'p> {
+ ModelLayouter {
+ layouter: LineLayouter::new(LineContext {
+ spaces: ctx.spaces.clone(),
+ axes: ctx.axes,
+ alignment: ctx.alignment,
+ repeat: ctx.repeat,
+ debug: ctx.debug,
+ line_spacing: ctx.style.text.line_spacing(),
+ }),
+ style: ctx.style.clone(),
+ ctx,
+ errors: vec![],
+ }
+ }
+
+ fn layout<'r>(
+ &'r mut self,
+ model: Spanned<&'r dyn Model>
+ ) -> DynFuture<'r, ()> { Box::pin(async move {
+ let layouted = model.v.layout(LayoutContext {
+ style: &self.style,
+ spaces: self.layouter.remaining(),
+ nested: true,
+ debug: false,
+ .. self.ctx
+ }).await;
+
+ let commands = layouted.output;
+ self.errors.extend(offset_spans(layouted.errors, model.span.start));
+
+ for command in commands {
+ self.execute_command(command, model.span);
+ }
+ }) }
+
+ fn execute_command<'r>(
+ &'r mut self,
+ command: Command<'r>,
+ model_span: Span,
+ ) -> DynFuture<'r, ()> { Box::pin(async move {
+ use Command::*;
+
+ match command {
+ LayoutSyntaxModel(model) => self.layout_syntax_model(model).await,
+
+ Add(layout) => self.layouter.add(layout),
+ AddMultiple(layouts) => self.layouter.add_multiple(layouts),
+ SpacingFunc(space, kind, axis) => match axis {
+ Primary => self.layouter.add_primary_spacing(space, kind),
+ Secondary => self.layouter.add_secondary_spacing(space, kind),
+ }
+
+ FinishLine => self.layouter.finish_line(),
+ FinishSpace => self.layouter.finish_space(true),
+ BreakParagraph => self.layout_paragraph(),
+ BreakPage => {
+ if self.ctx.nested {
+ self.errors.push(Spanned::new(
+ Error::new( "page break cannot be issued from nested context"),
+ model_span,
+ ));
+ } else {
+ self.layouter.finish_space(true)
+ }
+ }
+
+ SetTextStyle(style) => {
+ self.layouter.set_line_spacing(style.line_spacing());
+ self.style.text = style;
+ }
+ SetPageStyle(style) => {
+ if self.ctx.nested {
+ self.errors.push(Spanned::new(
+ Error::new("page style cannot be changed from nested context"),
+ model_span,
+ ));
+ } else {
+ self.style.page = style;
+
+ let margins = style.margins();
+ self.ctx.base = style.dimensions.unpadded(margins);
+ self.layouter.set_spaces(smallvec![
+ LayoutSpace {
+ dimensions: style.dimensions,
+ padding: margins,
+ expansion: LayoutExpansion::new(true, true),
+ }
+ ], true);
+ }
+ }
+
+ SetAlignment(alignment) => self.ctx.alignment = alignment,
+ SetAxes(axes) => {
+ self.layouter.set_axes(axes);
+ self.ctx.axes = axes;
+ }
+ }
+ }) }
+
+ fn layout_syntax_model<'r>(
+ &'r mut self,
+ model: &'r SyntaxModel
+ ) -> DynFuture<'r, ()> { Box::pin(async move {
+ use Node::*;
+
+ for node in &model.nodes {
+ match &node.v {
+ Space => self.layout_space(),
+ Newline => self.layout_paragraph(),
+ Text(text) => self.layout_text(text).await,
+
+ ToggleItalic => self.style.text.variant.style.toggle(),
+ ToggleBolder => {
+ let fac = if self.style.text.bolder { -1 } else { 1 };
+ self.style.text.variant.weight.0 += 300 * fac;
+ self.style.text.bolder = !self.style.text.bolder;
+ }
+ ToggleMonospace => {
+ let list = &mut self.style.text.fallback.list;
+ match list.get(0).map(|s| s.as_str()) {
+ Some("monospace") => { list.remove(0); },
+ _ => list.insert(0, "monospace".to_string()),
+ }
+ }
+
+ Node::Model(model) => {
+ self.layout(Spanned::new(model.as_ref(), node.span)).await;
+ }
+ }
+ }
+ }) }
+
+ 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,
+ alignment: self.ctx.alignment,
+ }).await)
+ }
+
+ fn layout_space(&mut self) {
+ self.layouter.add_primary_spacing(
+ self.style.text.word_spacing(),
+ SpacingKind::WORD,
+ );
+ }
+
+ fn layout_paragraph(&mut self) {
+ self.layouter.add_secondary_spacing(
+ self.style.text.paragraph_spacing(),
+ SpacingKind::PARAGRAPH,
+ );
+ }
+
+ fn finish(self) -> Layouted<MultiLayout> {
+ Layouted {
+ output: self.layouter.finish(),
+ errors: self.errors,
+ }
+ }
+}
diff --git a/src/layout/stack.rs b/src/layout/stack.rs
index 80d57424..96b44d04 100644
--- a/src/layout/stack.rs
+++ b/src/layout/stack.rs
@@ -68,10 +68,12 @@ impl StackLayouter {
}
/// Add a layout to the stack.
- pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
- // If the alignment cannot be fit in this space, finish it.
- if !self.update_rulers(layout.alignment) {
- self.finish_space(true)?;
+ pub fn add(&mut self, layout: Layout) {
+ // If the alignment cannot be fitted in this space, finish it.
+ // TODO: Issue warning for non-fitting alignment in
+ // non-repeating context.
+ if !self.update_rulers(layout.alignment) && self.ctx.repeat {
+ self.finish_space(true);
}
// Now, we add a possibly cached soft space. If the secondary alignment
@@ -81,14 +83,9 @@ impl StackLayouter {
self.add_spacing(spacing, SpacingKind::Hard);
}
- // Find the first space that fits the layout.
- while !self.space.usable.fits(layout.dimensions) {
- if self.space_is_last() && self.space_is_empty() {
- error!("cannot fit box of size {} into usable size of {}",
- layout.dimensions, self.space.usable);
- }
-
- self.finish_space(true)?;
+ // TODO: Issue warning about overflow if there is overflow.
+ if !self.space.usable.fits(layout.dimensions) && self.ctx.repeat {
+ self.skip_to_fitting_space(layout.dimensions);
}
// Change the usable space and size of the space.
@@ -98,18 +95,15 @@ impl StackLayouter {
// again.
self.space.layouts.push((self.ctx.axes, layout));
self.space.last_spacing = LastSpacing::None;
-
- Ok(())
}
/// Add multiple layouts to the stack.
///
/// This function simply calls `add` repeatedly for each layout.
- pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
+ pub fn add_multiple(&mut self, layouts: MultiLayout) {
for layout in layouts {
- self.add(layout)?;
+ self.add(layout);
}
- Ok(())
}
/// Add secondary spacing to the stack.
@@ -215,6 +209,19 @@ impl StackLayouter {
}
}
+ /// Move to the first space that can fit the given dimensions or do nothing
+ /// if no space is capable of that.
+ pub fn skip_to_fitting_space(&mut self, dimensions: Size2D) {
+ let start = self.next_space();
+ for (index, space) in self.ctx.spaces[start..].iter().enumerate() {
+ if space.usable().fits(dimensions) {
+ self.finish_space(true);
+ self.start_space(start + index, true);
+ return;
+ }
+ }
+ }
+
/// The remaining unpadded, unexpanding spaces. If a multi-layout is laid
/// out into these spaces, it will fit into this stack.
pub fn remaining(&self) -> LayoutSpaces {
@@ -251,19 +258,15 @@ impl StackLayouter {
}
/// Compute the finished multi-layout.
- pub fn finish(mut self) -> LayoutResult<MultiLayout> {
+ pub fn finish(mut self) -> MultiLayout {
if self.space.hard || !self.space_is_empty() {
- self.finish_space(false)?;
+ self.finish_space(false);
}
- Ok(self.layouts)
+ self.layouts
}
/// Finish the current space and start a new one.
- pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> {
- if !self.ctx.repeat && hard {
- error!("cannot create new space in a non-repeating context");
- }
-
+ pub fn finish_space(&mut self, hard: bool) {
let space = self.ctx.spaces[self.space.index];
// ------------------------------------------------------------------ //
@@ -376,7 +379,7 @@ impl StackLayouter {
// ------------------------------------------------------------------ //
// Step 5: Start the next space.
- Ok(self.start_space(self.next_space(), hard))
+ self.start_space(self.next_space(), hard)
}
/// Start a new space with the given index.
diff --git a/src/layout/text.rs b/src/layout/text.rs
index a66e04a2..16ae93da 100644
--- a/src/layout/text.rs
+++ b/src/layout/text.rs
@@ -6,6 +6,14 @@ use crate::style::TextStyle;
use super::*;
+/// 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
+}
+
/// The context for text layouting.
///
/// See [`LayoutContext`] for details about the fields.
@@ -17,14 +25,6 @@ pub struct TextContext<'a, 'p> {
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<'_, '_>) -> LayoutResult<Layout> {
- TextLayouter::new(text, ctx).layout().await
-}
-
/// Layouts text into boxes.
struct TextLayouter<'a, 'p> {
ctx: TextContext<'a, 'p>,
@@ -49,14 +49,14 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
}
/// Layout the text
- async fn layout(mut self) -> LayoutResult<Layout> {
+ async fn layout(mut self) -> Layout {
if self.ctx.axes.primary.is_positive() {
for c in self.text.chars() {
- self.layout_char(c).await?;
+ self.layout_char(c).await;
}
} else {
for c in self.text.chars().rev() {
- self.layout_char(c).await?;
+ self.layout_char(c).await;
}
}
@@ -64,16 +64,20 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
self.actions.add(LayoutAction::WriteText(self.buffer));
}
- Ok(Layout {
+ Layout {
dimensions: Size2D::new(self.width, self.ctx.style.font_size()),
alignment: self.ctx.alignment,
actions: self.actions.to_vec(),
- })
+ }
}
/// Layout an individual character.
- async fn layout_char(&mut self, c: char) -> LayoutResult<()> {
- let (index, char_width) = self.select_font(c).await?;
+ async fn layout_char(&mut self, c: char) {
+ let (index, char_width) = match self.select_font(c).await {
+ Some(selected) => selected,
+ // TODO: Issue warning about missing character.
+ None => return,
+ };
self.width += char_width;
@@ -88,13 +92,11 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
}
self.buffer.push(c);
-
- Ok(())
}
/// Select the best font for a character and return its index along with
/// the width of the char in the font.
- async fn select_font(&mut self, c: char) -> LayoutResult<(FontIndex, Size)> {
+ async fn select_font(&mut self, c: char) -> Option<(FontIndex, Size)> {
let mut loader = self.ctx.loader.borrow_mut();
let query = FontQuery {
@@ -104,26 +106,27 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
};
if let Some((font, index)) = loader.get(query).await {
- let font_unit_ratio = 1.0 / (font.read_table::<Header>()?.units_per_em as f32);
+ 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);
let glyph = font
- .read_table::<CharMap>()?
- .get(c)
- .expect("select_font: font should have char");
+ .read_table::<CharMap>()
+ .ok()?
+ .get(c)?;
let glyph_width = font
- .read_table::<HorizontalMetrics>()?
- .get(glyph)
- .expect("select_font: font should have glyph")
+ .read_table::<HorizontalMetrics>()
+ .ok()?
+ .get(glyph)?
.advance_width as f32;
let char_width = font_unit_to_size(glyph_width)
* self.ctx.style.font_size().to_pt();
- return Ok((index, char_width));
+ Some((index, char_width))
+ } else {
+ None
}
-
- error!("no suitable font for character `{}`", c);
}
}
diff --git a/src/layout/tree.rs b/src/layout/tree.rs
deleted file mode 100644
index 86b00f22..00000000
--- a/src/layout/tree.rs
+++ /dev/null
@@ -1,163 +0,0 @@
-use std::pin::Pin;
-use std::future::Future;
-use smallvec::smallvec;
-
-use crate::func::Command;
-use crate::syntax::{SyntaxTree, Node, FuncCall};
-use super::*;
-
-
-type RecursiveResult<'a, T> = Pin<Box<dyn Future<Output=LayoutResult<T>> + 'a>>;
-
-/// Layout a syntax tree into a multibox.
-pub async fn layout(tree: &SyntaxTree, ctx: LayoutContext<'_, '_>) -> LayoutResult<MultiLayout> {
- let mut layouter = TreeLayouter::new(ctx);
- layouter.layout(tree).await?;
- layouter.finish()
-}
-
-#[derive(Debug, Clone)]
-struct TreeLayouter<'a, 'p> {
- ctx: LayoutContext<'a, 'p>,
- layouter: LineLayouter,
- style: LayoutStyle,
-}
-
-impl<'a, 'p> TreeLayouter<'a, 'p> {
- /// Create a new syntax tree layouter.
- fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> {
- TreeLayouter {
- layouter: LineLayouter::new(LineContext {
- spaces: ctx.spaces.clone(),
- axes: ctx.axes,
- alignment: ctx.alignment,
- repeat: ctx.repeat,
- debug: ctx.debug,
- line_spacing: ctx.style.text.line_spacing(),
- }),
- style: ctx.style.clone(),
- ctx,
- }
- }
-
- fn layout<'b>(&'b mut self, tree: &'b SyntaxTree) -> RecursiveResult<'b, ()> {
- Box::pin(async move {
- for node in &tree.nodes {
- match &node.v {
- Node::Text(text) => self.layout_text(text).await?,
-
- Node::Space => self.layout_space(),
- Node::Newline => self.layout_paragraph()?,
-
- Node::ToggleItalic => self.style.text.variant.style.toggle(),
- Node::ToggleBolder => {
- self.style.text.variant.weight.0 += 300 *
- if self.style.text.bolder { -1 } else { 1 };
- self.style.text.bolder = !self.style.text.bolder;
- }
- Node::ToggleMonospace => {
- let list = &mut self.style.text.fallback.list;
- match list.get(0).map(|s| s.as_str()) {
- Some("monospace") => { list.remove(0); },
- _ => list.insert(0, "monospace".to_string()),
- }
- }
-
- Node::Func(func) => self.layout_func(func).await?,
- }
- }
-
- Ok(())
- })
- }
-
- async fn layout_text(&mut self, text: &str) -> LayoutResult<()> {
- let layout = layout_text(text, TextContext {
- loader: &self.ctx.loader,
- style: &self.style.text,
- axes: self.ctx.axes,
- alignment: self.ctx.alignment,
- }).await?;
-
- self.layouter.add(layout)
- }
-
- fn layout_space(&mut self) {
- self.layouter.add_primary_spacing(self.style.text.word_spacing(), WORD_KIND);
- }
-
- fn layout_paragraph(&mut self) -> LayoutResult<()> {
- self.layouter.add_secondary_spacing(self.style.text.paragraph_spacing(), PARAGRAPH_KIND)
- }
-
- fn layout_func<'b>(&'b mut self, func: &'b FuncCall) -> RecursiveResult<'b, ()> {
- Box::pin(async move {
- let commands = func.0.layout(LayoutContext {
- style: &self.style,
- spaces: self.layouter.remaining(),
- nested: true,
- debug: false,
- .. self.ctx
- }).await?;
-
- for command in commands {
- use Command::*;
-
- match command {
- LayoutTree(tree) => self.layout(tree).await?,
-
- Add(layout) => self.layouter.add(layout)?,
- AddMultiple(layouts) => self.layouter.add_multiple(layouts)?,
- SpacingFunc(space, kind, axis) => match axis {
- Primary => self.layouter.add_primary_spacing(space, kind),
- Secondary => self.layouter.add_secondary_spacing(space, kind)?,
- }
-
- FinishLine => self.layouter.finish_line()?,
- FinishSpace => self.layouter.finish_space(true)?,
- BreakParagraph => self.layout_paragraph()?,
- BreakPage => {
- if self.ctx.nested {
- error!("page break cannot be issued from nested context");
- }
-
- self.layouter.finish_space(true)?
- }
-
- SetTextStyle(style) => {
- self.layouter.set_line_spacing(style.line_spacing());
- self.style.text = style;
- }
- SetPageStyle(style) => {
- if self.ctx.nested {
- error!("page style cannot be altered in nested context");
- }
-
- self.style.page = style;
-
- let margins = style.margins();
- self.ctx.base = style.dimensions.unpadded(margins);
- self.layouter.set_spaces(smallvec![
- LayoutSpace {
- dimensions: style.dimensions,
- padding: margins,
- expansion: LayoutExpansion::new(true, true),
- }
- ], true);
- }
- SetAlignment(alignment) => self.ctx.alignment = alignment,
- SetAxes(axes) => {
- self.layouter.set_axes(axes)?;
- self.ctx.axes = axes;
- }
- }
- }
-
- Ok(())
- })
- }
-
- fn finish(self) -> LayoutResult<MultiLayout> {
- self.layouter.finish()
- }
-}