summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/export/pdf.rs10
-rw-r--r--src/func.rs6
-rw-r--r--src/layout/elements.rs3
-rw-r--r--src/layout/line.rs16
-rw-r--r--src/layout/mod.rs477
-rw-r--r--src/layout/model.rs313
-rw-r--r--src/layout/primitive.rs350
-rw-r--r--src/layout/stack.rs58
-rw-r--r--src/layout/text.rs8
-rw-r--r--src/layout/tree.rs223
-rw-r--r--src/lib.rs30
-rw-r--r--src/library/font.rs4
-rw-r--r--src/library/layout.rs14
-rw-r--r--src/library/mod.rs26
-rw-r--r--src/library/page.rs10
-rw-r--r--src/library/spacing.rs2
-rw-r--r--src/style.rs15
-rw-r--r--src/syntax/expr.rs6
-rw-r--r--src/syntax/mod.rs4
-rw-r--r--src/syntax/model.rs134
-rw-r--r--src/syntax/parsing.rs76
-rw-r--r--src/syntax/scope.rs12
-rw-r--r--src/syntax/test.rs18
-rw-r--r--src/syntax/tokens.rs4
-rw-r--r--src/syntax/tree.rs100
-rw-r--r--tests/test_typeset.rs12
26 files changed, 951 insertions, 980 deletions
diff --git a/src/export/pdf.rs b/src/export/pdf.rs
index 71665149..84023c71 100644
--- a/src/export/pdf.rs
+++ b/src/export/pdf.rs
@@ -15,7 +15,7 @@ use fontdock::FaceId;
use ttf_parser::{name_id, GlyphId};
use crate::SharedFontLoader;
-use crate::layout::{MultiLayout, Layout};
+use crate::layout::{MultiLayout, BoxLayout};
use crate::layout::elements::LayoutElement;
use crate::length::Length;
@@ -117,8 +117,8 @@ impl<'a, W: Write> PdfExporter<'a, W> {
let rect = Rect::new(
0.0,
0.0,
- Length::raw(page.dimensions.x).as_pt() as f32,
- Length::raw(page.dimensions.y).as_pt() as f32,
+ Length::raw(page.size.x).as_pt() as f32,
+ Length::raw(page.size.y).as_pt() as f32,
);
self.writer.write_obj(
@@ -141,7 +141,7 @@ impl<'a, W: Write> PdfExporter<'a, W> {
}
/// Write the content of a page.
- fn write_page(&mut self, id: u32, page: &Layout) -> io::Result<()> {
+ fn write_page(&mut self, id: u32, page: &BoxLayout) -> io::Result<()> {
// Moves and face switches are always cached and only flushed once
// needed.
let mut text = Text::new();
@@ -161,7 +161,7 @@ impl<'a, W: Write> PdfExporter<'a, W> {
}
let x = Length::raw(pos.x).as_pt();
- let y = Length::raw(page.dimensions.y - pos.y - size).as_pt();
+ let y = Length::raw(page.size.y - pos.y - size).as_pt();
text.tm(1.0, 0.0, 0.0, 1.0, x as f32, y as f32);
text.tj(shaped.encode_glyphs());
}
diff --git a/src/func.rs b/src/func.rs
index d3bc13cc..afc796ae 100644
--- a/src/func.rs
+++ b/src/func.rs
@@ -11,7 +11,7 @@ pub mod prelude {
pub use crate::layout::Command::{self, *};
pub use crate::style::{LayoutStyle, PageStyle, TextStyle};
pub use crate::syntax::expr::*;
- pub use crate::syntax::model::SyntaxModel;
+ pub use crate::syntax::tree::SyntaxTree;
pub use crate::syntax::span::{Span, Spanned};
pub use crate::syntax::value::*;
pub use super::OptionExt;
@@ -132,11 +132,11 @@ macro_rules! function {
};
(@layout($name:ident) layout($this:ident, $ctx:ident, $feedback:ident) $code:block) => {
- impl $crate::syntax::model::Model for $name {
+ impl $crate::layout::Layout for $name {
fn layout<'a, 'b, 't>(
#[allow(unused)] &'a $this,
#[allow(unused)] mut $ctx: $crate::layout::LayoutContext<'b>,
- ) -> $crate::layout::DynFuture<'t, $crate::Pass<$crate::layout::Commands<'a>>>
+ ) -> $crate::DynFuture<'t, $crate::Pass<$crate::layout::Commands<'a>>>
where
'a: 't,
'b: 't,
diff --git a/src/layout/elements.rs b/src/layout/elements.rs
index e524e1fd..92b53ae8 100644
--- a/src/layout/elements.rs
+++ b/src/layout/elements.rs
@@ -35,8 +35,7 @@ impl Default for LayoutElements {
}
}
-/// A layouting action, which is the basic building block layouts are composed
-/// of.
+/// A layout element, which is the basic building block layouts are composed of.
#[derive(Debug, Clone, PartialEq)]
pub enum LayoutElement {
/// Shaped text.
diff --git a/src/layout/line.rs b/src/layout/line.rs
index 6b2fd3c6..358d2ac9 100644
--- a/src/layout/line.rs
+++ b/src/layout/line.rs
@@ -45,7 +45,7 @@ pub struct LineContext {
#[derive(Debug)]
struct LineRun {
/// The so-far accumulated layouts in the line.
- layouts: Vec<(f64, Layout)>,
+ layouts: Vec<(f64, BoxLayout)>,
/// The width and maximal height of the line.
size: Size,
/// The alignment of all layouts in the line.
@@ -77,7 +77,7 @@ impl LineLayouter {
}
/// Add a layout to the run.
- pub fn add(&mut self, layout: Layout) {
+ pub fn add(&mut self, layout: BoxLayout) {
let axes = self.ctx.axes;
if let Some(align) = self.run.align {
@@ -116,7 +116,7 @@ impl LineLayouter {
self.add_primary_spacing(spacing, SpacingKind::Hard);
}
- let size = layout.dimensions.generalized(axes);
+ let size = layout.size.generalized(axes);
if !self.usable().fits(size) {
if !self.line_is_empty() {
@@ -125,7 +125,7 @@ impl LineLayouter {
// TODO: Issue warning about overflow if there is overflow.
if !self.usable().fits(size) {
- self.stack.skip_to_fitting_space(layout.dimensions);
+ self.stack.skip_to_fitting_space(layout.size);
}
}
@@ -222,7 +222,7 @@ impl LineLayouter {
/// a function how much space it has to layout itself.
pub fn remaining(&self) -> LayoutSpaces {
let mut spaces = self.stack.remaining();
- *spaces[0].dimensions.secondary_mut(self.ctx.axes)
+ *spaces[0].size.secondary_mut(self.ctx.axes)
-= self.run.size.y;
spaces
}
@@ -256,15 +256,15 @@ impl LineLayouter {
true => offset,
false => self.run.size.x
- offset
- - layout.dimensions.primary(self.ctx.axes),
+ - layout.size.primary(self.ctx.axes),
};
let pos = Size::with_x(x);
elements.extend_offset(pos, layout.elements);
}
- self.stack.add(Layout {
- dimensions: self.run.size.specialized(self.ctx.axes),
+ self.stack.add(BoxLayout {
+ size: self.run.size.specialized(self.ctx.axes),
align: self.run.align
.unwrap_or(LayoutAlign::new(Start, Start)),
elements
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 41a314f0..143f1984 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -1,57 +1,100 @@
//! Layouting types and engines.
-use std::fmt::{self, Display, Formatter};
+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::*;
+pub mod elements;
pub mod line;
+pub mod primitive;
pub mod stack;
pub mod text;
-pub mod elements;
-pub_use_mod!(model);
+pub mod tree;
+
+pub use primitive::*;
/// Basic types used across the layouting engine.
pub mod prelude {
- pub use super::{
- layout, LayoutContext, LayoutSpace, Command, Commands,
- LayoutAxes, LayoutAlign, LayoutExpansion,
- };
- pub use super::Dir::{self, *};
- pub use super::GenAxis::{self, *};
- pub use super::SpecAxis::{self, *};
- pub use super::GenAlign::{self, *};
- pub use super::SpecAlign::{self, *};
+ pub use super::layout;
+ pub use super::primitive::*;
+ pub use Dir::*;
+ pub use GenAxis::*;
+ pub use SpecAxis::*;
+ pub use GenAlign::*;
+ pub use SpecAlign::*;
}
/// A collection of layouts.
-pub type MultiLayout = Vec<Layout>;
+pub type MultiLayout = Vec<BoxLayout>;
/// A finished box with content at fixed positions.
#[derive(Debug, Clone, PartialEq)]
-pub struct Layout {
+pub struct BoxLayout {
/// The size of the box.
- pub dimensions: Size,
+ pub size: Size,
/// How to align this layout in a parent container.
pub align: LayoutAlign,
- /// The actions composing this layout.
+ /// The elements composing this layout.
pub elements: LayoutElements,
}
-/// A vector of layout spaces, that is stack allocated as long as it only
-/// contains at most 2 spaces.
+/// Layouting of elements.
+#[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()
+}
+
+/// The context for layouting.
+#[derive(Debug, Clone)]
+pub struct LayoutContext<'a> {
+ /// The font loader to retrieve fonts from when typesetting text
+ /// using [`layout_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).
+ pub base: Size,
+ /// The spaces to layout in.
+ pub spaces: LayoutSpaces,
+ /// Whether to have repeated spaces or to use only the first and only once.
+ pub repeat: bool,
+ /// The initial axes along which content is laid out.
+ pub axes: LayoutAxes,
+ /// The alignment of the finished layout.
+ pub align: LayoutAlign,
+ /// Whether the layout that is to be created will be nested in a parent
+ /// container.
+ pub nested: bool,
+}
+
+/// A collection of layout spaces.
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.
- pub dimensions: Size,
+ pub size: Size,
/// Padding that should be respected on each side.
pub padding: Margins,
- /// Whether to expand the dimensions of the resulting layout to the full
- /// dimensions of this space or to shrink them to fit the content.
+ /// Whether to expand the size of the resulting layout to the full size of
+ /// this space or to shrink them to fit the content.
pub expansion: LayoutExpansion,
}
@@ -62,363 +105,63 @@ impl LayoutSpace {
Size::new(self.padding.left, self.padding.top)
}
- /// The actually usable area (dimensions minus padding).
+ /// The actually usable area (size minus padding).
pub fn usable(&self) -> Size {
- self.dimensions.unpadded(self.padding)
+ self.size.unpadded(self.padding)
}
- /// A layout space without padding and dimensions reduced by the padding.
+ /// A layout space without padding and size reduced by the padding.
pub fn usable_space(&self) -> LayoutSpace {
LayoutSpace {
- dimensions: self.usable(),
+ size: self.usable(),
padding: Margins::ZERO,
expansion: LayoutExpansion::new(false, false),
}
}
}
-/// Specifies along which axes 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.
- ///
- /// # Panics
- /// This function panics if the axes are aligned, that is, they are
- /// on the same axis.
- pub fn new(primary: Dir, secondary: Dir) -> LayoutAxes {
- if primary.axis() == secondary.axis() {
- panic!("invalid aligned axes {} and {}", primary, secondary);
- }
-
- LayoutAxes { primary, secondary }
- }
-
- /// Return the direction of the specified generic axis.
- pub fn get(self, axis: GenAxis) -> Dir {
- match axis {
- Primary => self.primary,
- Secondary => self.secondary,
- }
- }
-
- /// Borrow the direction of the specified generic axis mutably.
- pub fn get_mut(&mut self, axis: GenAxis) -> &mut Dir {
- match axis {
- Primary => &mut self.primary,
- Secondary => &mut self.secondary,
- }
- }
-}
-
-/// Directions along which content is laid out.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum Dir {
- LTT,
- RTL,
- TTB,
- BTT,
-}
-
-impl Dir {
- /// The specific axis this direction belongs to.
- pub fn axis(self) -> SpecAxis {
- match self {
- LTT | RTL => Horizontal,
- TTB | BTT => Vertical,
- }
- }
-
- /// Whether this axis points into the positive coordinate direction.
- ///
- /// The positive axes are left-to-right and top-to-bottom.
- pub fn is_positive(self) -> bool {
- match self {
- LTT | TTB => true,
- RTL | BTT => false,
- }
- }
-
- /// The factor for this direction.
- ///
- /// - `1` if the direction is positive.
- /// - `-1` 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 {
- match self {
- LTT => RTL,
- RTL => LTT,
- TTB => BTT,
- BTT => TTB,
- }
- }
-}
-
-impl Display for Dir {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.pad(match self {
- LTT => "ltr",
- RTL => "rtl",
- TTB => "ttb",
- BTT => "btt",
- })
- }
-}
-
-/// The two generic layouting axes.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum GenAxis {
- /// The primary axis along which words are laid out.
- Primary,
- /// The secondary axis along which lines and paragraphs are laid out.
- Secondary,
-}
-
-impl GenAxis {
- /// The specific version of this axis in the given system of axes.
- pub fn to_specific(self, axes: LayoutAxes) -> SpecAxis {
- axes.get(self).axis()
- }
-}
-
-impl Display for GenAxis {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.pad(match self {
- Primary => "primary",
- Secondary => "secondary",
- })
- }
-}
-
-/// The two specific layouting axes.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum SpecAxis {
- /// The horizontal layouting axis.
- Horizontal,
- /// The vertical layouting axis.
- Vertical,
-}
-
-impl SpecAxis {
- /// The generic version of this axis in the given system of axes.
- pub fn to_generic(self, axes: LayoutAxes) -> GenAxis {
- if self == axes.primary.axis() { Primary } else { Secondary }
- }
-}
-
-impl Display for SpecAxis {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.pad(match self {
- Horizontal => "horizontal",
- Vertical => "vertical",
- })
- }
-}
-
-/// 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 }
- }
-
- /// Return the alignment of the specified generic axis.
- pub fn get(self, axis: GenAxis) -> GenAlign {
- match axis {
- Primary => self.primary,
- Secondary => self.secondary,
- }
- }
-
- /// Borrow the alignment of the specified generic axis mutably.
- pub fn get_mut(&mut self, axis: GenAxis) -> &mut GenAlign {
- match axis {
- Primary => &mut self.primary,
- Secondary => &mut self.secondary,
- }
- }
-}
-
-/// Where to align content along a generic context.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
-pub enum GenAlign {
- Start,
- Center,
- End,
-}
-
-impl GenAlign {
- /// The inverse alignment.
- pub fn inv(self) -> GenAlign {
- match self {
- Start => End,
- Center => Center,
- End => Start,
- }
- }
-}
-
-impl Display for GenAlign {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.pad(match self {
- Start => "start",
- Center => "center",
- End => "end",
- })
- }
-}
-
-/// Where to align content in a specific context.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
-pub enum SpecAlign {
- Left,
- Right,
- Top,
- Bottom,
- Center,
-}
+/// A sequence of layouting commands.
+pub type Commands<'a> = Vec<Command<'a>>;
-impl SpecAlign {
- /// The specific axis this alignment refers to.
+/// Commands issued to the layouting engine by trees.
+#[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.
///
- /// Returns `None` if this is center.
- pub fn axis(self) -> Option<SpecAxis> {
- match self {
- Self::Left => Some(Horizontal),
- Self::Right => Some(Horizontal),
- Self::Top => Some(Vertical),
- Self::Bottom => Some(Vertical),
- Self::Center => None,
- }
- }
-
- /// Convert this to a generic alignment.
- pub fn to_generic(self, axes: LayoutAxes) -> GenAlign {
- let get = |spec: SpecAxis, align: GenAlign| {
- let axis = spec.to_generic(axes);
- if axes.get(axis).is_positive() { align } else { align.inv() }
- };
-
- match self {
- Self::Left => get(Horizontal, Start),
- Self::Right => get(Horizontal, End),
- Self::Top => get(Vertical, Start),
- Self::Bottom => get(Vertical, End),
- Self::Center => GenAlign::Center,
- }
- }
-}
-
-impl Display for SpecAlign {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.pad(match self {
- Self::Left => "left",
- Self::Right => "right",
- Self::Top => "top",
- Self::Bottom => "bottom",
- Self::Center => "center",
- })
- }
-}
-
-/// 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: SpecAxis) -> 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: SpecAxis) -> &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 {
- /// Hard spaces are always laid out and consume surrounding soft space.
- Hard,
- /// Soft spaces are not laid out if they are touching a hard space and
- /// consume neighbouring soft spaces with higher levels.
- Soft(u32),
-}
-
-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.
- pub const LINE: SpacingKind = SpacingKind::Soft(2);
-
- /// The standard spacing kind used for word spacing.
- pub const WORD: SpacingKind = SpacingKind::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`.
-#[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(f64, u32),
- /// The last item was not spacing.
- None,
-}
-
-impl LastSpacing {
- /// The width of the soft space if this is a soft space or zero otherwise.
- fn soft_or_zero(self) -> f64 {
- match self {
- LastSpacing::Soft(space, _) => space,
- _ => 0.0,
- }
- }
+ /// 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.
+ LayoutSyntaxTree(&'a SyntaxTree),
+
+ /// Add a already computed layout.
+ Add(BoxLayout),
+ /// 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(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
+ /// 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(LayoutAlign),
+ /// Update the layouting axes along which future boxes will be laid
+ /// out. This finishes the current line.
+ SetAxes(LayoutAxes),
}
diff --git a/src/layout/model.rs b/src/layout/model.rs
deleted file mode 100644
index db069870..00000000
--- a/src/layout/model.rs
+++ /dev/null
@@ -1,313 +0,0 @@
-//! 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 crate::{Pass, Feedback};
-use crate::SharedFontLoader;
-use crate::style::{LayoutStyle, PageStyle, TextStyle};
-use crate::geom::Size;
-use crate::syntax::decoration::Decoration;
-use crate::syntax::model::{Model, SyntaxModel, Node};
-use crate::syntax::span::{Span, Spanned};
-use super::line::{LineLayouter, LineContext};
-use super::text::{layout_text, TextContext};
-use super::*;
-
-/// Performs the model layouting.
-#[derive(Debug)]
-pub struct ModelLayouter<'a> {
- ctx: LayoutContext<'a>,
- layouter: LineLayouter,
- style: LayoutStyle,
- feedback: Feedback,
-}
-
-/// The context for layouting.
-#[derive(Debug, Clone)]
-pub struct LayoutContext<'a> {
- /// The font loader to retrieve fonts from when typesetting text
- /// using [`layout_text`].
- pub loader: &'a SharedFontLoader,
- /// The style for pages and text.
- pub style: &'a LayoutStyle,
- /// The base unpadded dimensions of this container (for relative sizing).
- pub base: Size,
- /// The spaces to layout in.
- pub spaces: LayoutSpaces,
- /// Whether to have repeated spaces or to use only the first and only once.
- pub repeat: bool,
- /// The initial axes along which content is laid out.
- pub axes: LayoutAxes,
- /// The alignment of the finished layout.
- pub align: LayoutAlign,
- /// Whether the layout that is to be created will be nested in a parent
- /// container.
- pub nested: bool,
-}
-
-/// A sequence of layouting commands.
-pub type Commands<'a> = Vec<Command<'a>>;
-
-/// Commands issued to the layouting engine by models.
-#[derive(Debug, Clone)]
-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(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
- /// 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(LayoutAlign),
- /// 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<'_>) -> Pass<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> ModelLayouter<'a> {
- /// Create a new model layouter.
- pub fn new(ctx: LayoutContext<'a>) -> ModelLayouter<'a> {
- ModelLayouter {
- layouter: LineLayouter::new(LineContext {
- spaces: ctx.spaces.clone(),
- axes: ctx.axes,
- align: ctx.align,
- repeat: ctx.repeat,
- line_spacing: ctx.style.text.line_spacing(),
- }),
- style: ctx.style.clone(),
- ctx,
- feedback: Feedback::new(),
- }
- }
-
- /// Flatly layout a model into this layouting process.
- pub async fn layout<'r>(
- &'r mut self,
- model: Spanned<&'r dyn Model>
- ) {
- // Execute the model's layout function which generates the commands.
- let layouted = model.v.layout(LayoutContext {
- style: &self.style,
- spaces: self.layouter.remaining(),
- nested: true,
- .. self.ctx
- }).await;
-
- // Add the errors generated by the model to the error list.
- self.feedback.extend_offset(layouted.feedback, model.span.start);
-
- 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 async fn layout_syntax_model<'r>(
- &'r mut self,
- model: &'r SyntaxModel
- ) {
- use Node::*;
-
- for Spanned { v: node, span } in &model.nodes {
- let decorate = |this: &mut ModelLayouter, deco| {
- this.feedback.decorations.push(Spanned::new(deco, *span));
- };
-
- match node {
- Space => self.layout_space(),
- Parbreak => self.layout_paragraph(),
- Linebreak => self.layouter.finish_line(),
-
- Text(text) => {
- if self.style.text.italic {
- decorate(self, Decoration::Italic);
- }
-
- if self.style.text.bolder {
- decorate(self, Decoration::Bold);
- }
-
- self.layout_text(text).await;
- }
-
- ToggleItalic => {
- self.style.text.italic = !self.style.text.italic;
- decorate(self, Decoration::Italic);
- }
-
- ToggleBolder => {
- self.style.text.bolder = !self.style.text.bolder;
- decorate(self, Decoration::Bold);
- }
-
- 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.flatten();
-
- // Layout the first line.
- let mut iter = lines.iter();
- if let Some(line) = iter.next() {
- self.layout_text(line).await;
- }
-
- // Put a newline before each following line.
- for line in iter {
- self.layouter.finish_line();
- self.layout_text(line).await;
- }
-
- self.style.text.fallback = fallback;
- }
-
- Model(model) => {
- self.layout(Spanned::new(model.as_ref(), *span)).await;
- }
- }
- }
- }
-
- /// 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 model. When the command is errorful, the
- /// given span is stored with the error.
- 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),
- AddSpacing(space, kind, axis) => match axis {
- Primary => self.layouter.add_primary_spacing(space, kind),
- Secondary => self.layouter.add_secondary_spacing(space, kind),
- }
-
- BreakLine => self.layouter.finish_line(),
- BreakParagraph => self.layout_paragraph(),
- BreakPage => {
- if self.ctx.nested {
- error!(
- @self.feedback, model_span,
- "page break cannot be issued from nested context",
- );
- } 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 {
- error!(
- @self.feedback, model_span,
- "page style cannot be changed from nested context",
- );
- } 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(vec![
- LayoutSpace {
- dimensions: style.dimensions,
- padding: margins,
- expansion: LayoutExpansion::new(true, true),
- }
- ], true);
- }
- }
-
- SetAlignment(align) => self.ctx.align = align,
- SetAxes(axes) => {
- self.layouter.set_axes(axes);
- self.ctx.axes = axes;
- }
- }
- }) }
-
- /// 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)
- }
-
- /// Add the spacing for a syntactic space node.
- fn layout_space(&mut self) {
- self.layouter.add_primary_spacing(
- self.style.text.word_spacing(),
- SpacingKind::WORD,
- );
- }
-
- /// Finish the paragraph and add paragraph spacing.
- fn layout_paragraph(&mut self) {
- self.layouter.add_secondary_spacing(
- self.style.text.paragraph_spacing(),
- SpacingKind::PARAGRAPH,
- );
- }
-}
diff --git a/src/layout/primitive.rs b/src/layout/primitive.rs
new file mode 100644
index 00000000..2eb5669b
--- /dev/null
+++ b/src/layout/primitive.rs
@@ -0,0 +1,350 @@
+//! Layouting primitives.
+
+use std::fmt::{self, Display, Formatter};
+use super::prelude::*;
+
+/// Specifies along which axes 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.
+ ///
+ /// # Panics
+ /// This function panics if the axes are aligned, that is, they are
+ /// on the same axis.
+ pub fn new(primary: Dir, secondary: Dir) -> LayoutAxes {
+ if primary.axis() == secondary.axis() {
+ panic!("invalid aligned axes {} and {}", primary, secondary);
+ }
+
+ LayoutAxes { primary, secondary }
+ }
+
+ /// Return the direction of the specified generic axis.
+ pub fn get(self, axis: GenAxis) -> Dir {
+ match axis {
+ Primary => self.primary,
+ Secondary => self.secondary,
+ }
+ }
+
+ /// Borrow the direction of the specified generic axis mutably.
+ pub fn get_mut(&mut self, axis: GenAxis) -> &mut Dir {
+ match axis {
+ Primary => &mut self.primary,
+ Secondary => &mut self.secondary,
+ }
+ }
+}
+
+/// Directions along which content is laid out.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum Dir {
+ LTT,
+ RTL,
+ TTB,
+ BTT,
+}
+
+impl Dir {
+ /// The specific axis this direction belongs to.
+ pub fn axis(self) -> SpecAxis {
+ match self {
+ LTT | RTL => Horizontal,
+ TTB | BTT => Vertical,
+ }
+ }
+
+ /// Whether this axis points into the positive coordinate direction.
+ ///
+ /// The positive axes are left-to-right and top-to-bottom.
+ pub fn is_positive(self) -> bool {
+ match self {
+ LTT | TTB => true,
+ RTL | BTT => false,
+ }
+ }
+
+ /// The factor for this direction.
+ ///
+ /// - `1` if the direction is positive.
+ /// - `-1` 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 {
+ match self {
+ LTT => RTL,
+ RTL => LTT,
+ TTB => BTT,
+ BTT => TTB,
+ }
+ }
+}
+
+impl Display for Dir {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ LTT => "ltr",
+ RTL => "rtl",
+ TTB => "ttb",
+ BTT => "btt",
+ })
+ }
+}
+
+/// The two generic layouting axes.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum GenAxis {
+ /// The primary axis along which words are laid out.
+ Primary,
+ /// The secondary axis along which lines and paragraphs are laid out.
+ Secondary,
+}
+
+impl GenAxis {
+ /// The specific version of this axis in the given system of axes.
+ pub fn to_specific(self, axes: LayoutAxes) -> SpecAxis {
+ axes.get(self).axis()
+ }
+}
+
+impl Display for GenAxis {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ Primary => "primary",
+ Secondary => "secondary",
+ })
+ }
+}
+
+/// The two specific layouting axes.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum SpecAxis {
+ /// The horizontal layouting axis.
+ Horizontal,
+ /// The vertical layouting axis.
+ Vertical,
+}
+
+impl SpecAxis {
+ /// The generic version of this axis in the given system of axes.
+ pub fn to_generic(self, axes: LayoutAxes) -> GenAxis {
+ if self == axes.primary.axis() { Primary } else { Secondary }
+ }
+}
+
+impl Display for SpecAxis {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ Horizontal => "horizontal",
+ Vertical => "vertical",
+ })
+ }
+}
+
+/// 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 }
+ }
+
+ /// Return the alignment of the specified generic axis.
+ pub fn get(self, axis: GenAxis) -> GenAlign {
+ match axis {
+ Primary => self.primary,
+ Secondary => self.secondary,
+ }
+ }
+
+ /// Borrow the alignment of the specified generic axis mutably.
+ pub fn get_mut(&mut self, axis: GenAxis) -> &mut GenAlign {
+ match axis {
+ Primary => &mut self.primary,
+ Secondary => &mut self.secondary,
+ }
+ }
+}
+
+/// Where to align content along a generic context.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub enum GenAlign {
+ Start,
+ Center,
+ End,
+}
+
+impl GenAlign {
+ /// The inverse alignment.
+ pub fn inv(self) -> GenAlign {
+ match self {
+ Start => End,
+ Center => Center,
+ End => Start,
+ }
+ }
+}
+
+impl Display for GenAlign {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ Start => "start",
+ Center => "center",
+ End => "end",
+ })
+ }
+}
+
+/// Where to align content in a specific context.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub enum SpecAlign {
+ Left,
+ Right,
+ Top,
+ Bottom,
+ Center,
+}
+
+impl SpecAlign {
+ /// The specific axis this alignment refers to.
+ ///
+ /// Returns `None` if this is center.
+ pub fn axis(self) -> Option<SpecAxis> {
+ match self {
+ Self::Left => Some(Horizontal),
+ Self::Right => Some(Horizontal),
+ Self::Top => Some(Vertical),
+ Self::Bottom => Some(Vertical),
+ Self::Center => None,
+ }
+ }
+
+ /// Convert this to a generic alignment.
+ pub fn to_generic(self, axes: LayoutAxes) -> GenAlign {
+ let get = |spec: SpecAxis, align: GenAlign| {
+ let axis = spec.to_generic(axes);
+ if axes.get(axis).is_positive() { align } else { align.inv() }
+ };
+
+ match self {
+ Self::Left => get(Horizontal, Start),
+ Self::Right => get(Horizontal, End),
+ Self::Top => get(Vertical, Start),
+ Self::Bottom => get(Vertical, End),
+ Self::Center => GenAlign::Center,
+ }
+ }
+}
+
+impl Display for SpecAlign {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ Self::Left => "left",
+ Self::Right => "right",
+ Self::Top => "top",
+ Self::Bottom => "bottom",
+ Self::Center => "center",
+ })
+ }
+}
+
+/// 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: SpecAxis) -> 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: SpecAxis) -> &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 {
+ /// Hard spaces are always laid out and consume surrounding soft space.
+ Hard,
+ /// Soft spaces are not laid out if they are touching a hard space and
+ /// consume neighbouring soft spaces with higher levels.
+ Soft(u32),
+}
+
+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.
+ pub const LINE: SpacingKind = SpacingKind::Soft(2);
+
+ /// The standard spacing kind used for word spacing.
+ pub const WORD: SpacingKind = SpacingKind::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`.
+#[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.
+ 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 {
+ match self {
+ LastSpacing::Soft(space, _) => space,
+ _ => 0.0,
+ }
+ }
+}
diff --git a/src/layout/stack.rs b/src/layout/stack.rs
index 4f4d3d8b..28da74b7 100644
--- a/src/layout/stack.rs
+++ b/src/layout/stack.rs
@@ -59,12 +59,12 @@ struct Space {
/// Whether to add the layout for this space even if it would be empty.
hard: bool,
/// The so-far accumulated layouts.
- layouts: Vec<(LayoutAxes, Layout)>,
+ layouts: Vec<(LayoutAxes, BoxLayout)>,
/// The specialized size of this space.
size: Size,
/// The specialized remaining space.
usable: Size,
- /// The specialized extra-needed dimensions to affect the size at all.
+ /// 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.
@@ -85,7 +85,7 @@ impl StackLayouter {
}
/// Add a layout to the stack.
- pub fn add(&mut self, layout: Layout) {
+ 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.
@@ -101,12 +101,12 @@ impl StackLayouter {
}
// 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);
+ if !self.space.usable.fits(layout.size) && self.ctx.repeat {
+ self.skip_to_fitting_space(layout.size);
}
// Change the usable space and size of the space.
- self.update_metrics(layout.dimensions.generalized(self.ctx.axes));
+ self.update_metrics(layout.size.generalized(self.ctx.axes));
// Add the box to the vector and remember that spacings are allowed
// again.
@@ -130,11 +130,11 @@ impl StackLayouter {
SpacingKind::Hard => {
// Reduce the spacing such that it definitely fits.
spacing = spacing.min(self.space.usable.secondary(self.ctx.axes));
- let dimensions = Size::with_y(spacing);
+ let size = Size::with_y(spacing);
- self.update_metrics(dimensions);
- self.space.layouts.push((self.ctx.axes, Layout {
- dimensions: dimensions.specialized(self.ctx.axes),
+ 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(),
}));
@@ -159,22 +159,22 @@ impl StackLayouter {
}
/// Update the size metrics to reflect that a layout or spacing with the
- /// given generalized dimensions has been added.
- fn update_metrics(&mut self, dimensions: Size) {
+ /// given generalized size has been added.
+ fn update_metrics(&mut self, added: Size) {
let axes = self.ctx.axes;
let mut size = self.space.size.generalized(axes);
let mut extra = self.space.extra.generalized(axes);
- size.x += (dimensions.x - extra.x).max(0.0);
- size.y += (dimensions.y - extra.y).max(0.0);
+ size.x += (added.x - extra.x).max(0.0);
+ size.y += (added.y - extra.y).max(0.0);
- extra.x = extra.x.max(dimensions.x);
- extra.y = (extra.y - dimensions.y).max(0.0);
+ extra.x = extra.x.max(added.x);
+ extra.y = (extra.y - added.y).max(0.0);
self.space.size = size.specialized(axes);
self.space.extra = extra.specialized(axes);
- *self.space.usable.secondary_mut(axes) -= dimensions.y;
+ *self.space.usable.secondary_mut(axes) -= added.y;
}
/// Update the rulers to account for the new layout. Returns true if a
@@ -226,12 +226,12 @@ impl StackLayouter {
}
}
- /// Move to the first space that can fit the given dimensions or do nothing
+ /// Move to the first space that can fit the given size or do nothing
/// if no space is capable of that.
- pub fn skip_to_fitting_space(&mut self, dimensions: Size) {
+ pub fn skip_to_fitting_space(&mut self, size: Size) {
let start = self.next_space();
for (index, space) in self.ctx.spaces[start..].iter().enumerate() {
- if space.usable().fits(dimensions) {
+ if space.usable().fits(size) {
self.finish_space(true);
self.start_space(start + index, true);
return;
@@ -242,10 +242,10 @@ impl StackLayouter {
/// 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();
+ let size = self.usable();
let mut spaces = vec![LayoutSpace {
- dimensions,
+ size,
padding: Margins::ZERO,
expansion: LayoutExpansion::new(false, false),
}];
@@ -287,7 +287,7 @@ impl StackLayouter {
let space = self.ctx.spaces[self.space.index];
// ------------------------------------------------------------------ //
- // Step 1: Determine the full dimensions of the space.
+ // Step 1: Determine the full size of the space.
// (Mostly done already while collecting the boxes, but here we
// expand if necessary.)
@@ -295,7 +295,7 @@ impl StackLayouter {
if space.expansion.horizontal { self.space.size.x = usable.x; }
if space.expansion.vertical { self.space.size.y = usable.y; }
- let dimensions = self.space.size.padded(space.padding);
+ let size = self.space.size.padded(space.padding);
// ------------------------------------------------------------------ //
// Step 2: Forward pass. Create a bounding box for each layout in which
@@ -323,7 +323,7 @@ impl StackLayouter {
// 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.dimensions.secondary(*axes);
+ += axes.secondary.factor() * layout.size.secondary(*axes);
}
// ------------------------------------------------------------------ //
@@ -355,7 +355,7 @@ impl StackLayouter {
-= axes.secondary.factor() * extent.y;
// Then, we add this layout's secondary extent to the accumulator.
- let size = layout.dimensions.generalized(*axes);
+ let size = layout.size.generalized(*axes);
extent.x = extent.x.max(size.x);
extent.y += size.y;
}
@@ -368,7 +368,7 @@ impl StackLayouter {
let layouts = std::mem::take(&mut self.space.layouts);
for ((axes, layout), bound) in layouts.into_iter().zip(bounds) {
- let size = layout.dimensions.specialized(axes);
+ let size = layout.size.specialized(axes);
let align = layout.align;
// The space in which this layout is aligned is given by the
@@ -383,8 +383,8 @@ impl StackLayouter {
elements.extend_offset(pos, layout.elements);
}
- self.layouts.push(Layout {
- dimensions,
+ self.layouts.push(BoxLayout {
+ size,
align: self.ctx.align,
elements,
});
diff --git a/src/layout/text.rs b/src/layout/text.rs
index 477099e2..5c18cd32 100644
--- a/src/layout/text.rs
+++ b/src/layout/text.rs
@@ -40,7 +40,7 @@ pub struct TextContext<'a> {
}
/// Layouts text into a box.
-pub async fn layout_text(text: &str, ctx: TextContext<'_>) -> Layout {
+pub async fn layout_text(text: &str, ctx: TextContext<'_>) -> BoxLayout {
TextLayouter::new(text, ctx).layout().await
}
@@ -58,7 +58,7 @@ impl<'a> TextLayouter<'a> {
}
/// Do the layouting.
- async fn layout(mut self) -> Layout {
+ async fn layout(mut self) -> BoxLayout {
// If the primary axis is negative, we layout the characters reversed.
if self.ctx.axes.primary.is_positive() {
for c in self.text.chars() {
@@ -76,8 +76,8 @@ impl<'a> TextLayouter<'a> {
self.elements.push(pos, LayoutElement::Text(self.shaped));
}
- Layout {
- dimensions: Size::new(self.width, self.ctx.style.font_size()),
+ BoxLayout {
+ size: Size::new(self.width, self.ctx.style.font_size()),
align: self.ctx.align,
elements: self.elements,
}
diff --git a/src/layout/tree.rs b/src/layout/tree.rs
new file mode 100644
index 00000000..44c59211
--- /dev/null
+++ b/src/layout/tree.rs
@@ -0,0 +1,223 @@
+//! The tree layouter layouts trees (i.e.
+//! [syntax trees](crate::syntax::SyntaxTree) and [functions](crate::func))
+//! by executing commands issued by the 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 super::text::{layout_text, TextContext};
+use super::*;
+
+/// Performs the tree layouting.
+#[derive(Debug)]
+pub struct TreeLayouter<'a> {
+ ctx: LayoutContext<'a>,
+ layouter: LineLayouter,
+ style: LayoutStyle,
+ feedback: Feedback,
+}
+
+impl<'a> TreeLayouter<'a> {
+ /// Create a new tree layouter.
+ pub fn new(ctx: LayoutContext<'a>) -> TreeLayouter<'a> {
+ TreeLayouter {
+ layouter: LineLayouter::new(LineContext {
+ spaces: ctx.spaces.clone(),
+ axes: ctx.axes,
+ align: ctx.align,
+ repeat: ctx.repeat,
+ line_spacing: ctx.style.text.line_spacing(),
+ }),
+ style: ctx.style.clone(),
+ ctx,
+ feedback: Feedback::new(),
+ }
+ }
+
+ /// 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) {
+ for node in tree {
+ self.layout_node(node).await;
+ }
+ }
+
+ pub async fn layout_node(&mut self, node: &Spanned<SyntaxNode>) {
+ let decorate = |this: &mut TreeLayouter, deco| {
+ this.feedback.decorations.push(Spanned::new(deco, node.span));
+ };
+
+ match &node.v {
+ SyntaxNode::Space => self.layout_space(),
+ SyntaxNode::Parbreak => self.layout_paragraph(),
+ SyntaxNode::Linebreak => self.layouter.finish_line(),
+
+ SyntaxNode::Text(text) => {
+ if self.style.text.italic {
+ decorate(self, Decoration::Italic);
+ }
+
+ if self.style.text.bolder {
+ decorate(self, Decoration::Bold);
+ }
+
+ self.layout_text(text).await;
+ }
+
+ SyntaxNode::ToggleItalic => {
+ self.style.text.italic = !self.style.text.italic;
+ decorate(self, Decoration::Italic);
+ }
+
+ SyntaxNode::ToggleBolder => {
+ self.style.text.bolder = !self.style.text.bolder;
+ decorate(self, Decoration::Bold);
+ }
+
+ 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.flatten();
+
+ // Layout the first line.
+ let mut iter = lines.iter();
+ if let Some(line) = iter.next() {
+ self.layout_text(line).await;
+ }
+
+ // Put a newline before each following line.
+ for line in iter {
+ self.layouter.finish_line();
+ self.layout_text(line).await;
+ }
+
+ self.style.text.fallback = fallback;
+ }
+
+ SyntaxNode::Dyn(dynamic) => {
+ self.layout_dyn(Spanned::new(dynamic.as_ref(), node.span)).await;
+ }
+ }
+ }
+
+ /// 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.
+ let layouted = dynamic.v.layout(LayoutContext {
+ style: &self.style,
+ spaces: self.layouter.remaining(),
+ nested: 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 {
+ self.execute_command(command, dynamic.span).await;
+ }
+ }
+
+ /// 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>,
+ tree_span: Span,
+ ) -> DynFuture<'r, ()> { Box::pin(async move {
+ use Command::*;
+
+ match command {
+ LayoutSyntaxTree(tree) => self.layout_tree(tree).await,
+
+ Add(layout) => self.layouter.add(layout),
+ AddMultiple(layouts) => self.layouter.add_multiple(layouts),
+ AddSpacing(space, kind, axis) => match axis {
+ Primary => self.layouter.add_primary_spacing(space, kind),
+ Secondary => self.layouter.add_secondary_spacing(space, kind),
+ }
+
+ BreakLine => self.layouter.finish_line(),
+ BreakParagraph => self.layout_paragraph(),
+ BreakPage => {
+ if self.ctx.nested {
+ error!(
+ @self.feedback, tree_span,
+ "page break cannot be issued from nested context",
+ );
+ } 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 {
+ error!(
+ @self.feedback, tree_span,
+ "page style cannot be changed from nested context",
+ );
+ } 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.size.unpadded(margins);
+ self.layouter.set_spaces(vec![
+ LayoutSpace {
+ size: style.size,
+ padding: margins,
+ expansion: LayoutExpansion::new(true, true),
+ }
+ ], true);
+ }
+ }
+
+ SetAlignment(align) => self.ctx.align = align,
+ SetAxes(axes) => {
+ self.layouter.set_axes(axes);
+ self.ctx.axes = axes;
+ }
+ }
+ }) }
+
+ /// 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)
+ }
+
+ /// Add the spacing for a syntactic space node.
+ fn layout_space(&mut self) {
+ self.layouter.add_primary_spacing(
+ self.style.text.word_spacing(),
+ SpacingKind::WORD,
+ );
+ }
+
+ /// Finish the paragraph and add paragraph spacing.
+ fn layout_paragraph(&mut self) {
+ self.layouter.add_secondary_spacing(
+ self.style.text.paragraph_spacing(),
+ SpacingKind::PARAGRAPH,
+ );
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 67b94adf..5a0b8d0b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,25 +16,19 @@
//! format is [_PDF_](crate::export::pdf).
use std::fmt::Debug;
+use std::future::Future;
+use std::pin::Pin;
use crate::diagnostic::Diagnostics;
use crate::font::SharedFontLoader;
use crate::layout::MultiLayout;
use crate::style::{LayoutStyle, PageStyle, TextStyle};
use crate::syntax::decoration::Decorations;
-use crate::syntax::model::SyntaxModel;
+use crate::syntax::tree::SyntaxTree;
use crate::syntax::parsing::{parse, ParseState};
use crate::syntax::scope::Scope;
use crate::syntax::span::{Offset, Pos};
-/// Declare a module and reexport all its contents.
-macro_rules! pub_use_mod {
- ($name:ident) => {
- mod $name;
- pub use $name::*;
- };
-}
-
#[macro_use]
mod macros;
#[macro_use]
@@ -84,23 +78,24 @@ impl Typesetter {
}
/// Parse source code into a syntax tree.
- pub fn parse(&self, src: &str) -> Pass<SyntaxModel> {
+ pub fn parse(&self, src: &str) -> Pass<SyntaxTree> {
parse(src, Pos::ZERO, &self.parse_state)
}
/// Layout a syntax tree and return the produced layout.
- pub async fn layout(&self, model: &SyntaxModel) -> Pass<MultiLayout> {
+ pub async fn layout(&self, tree: &SyntaxTree) -> Pass<MultiLayout> {
use crate::layout::prelude::*;
+ use crate::layout::{LayoutContext, LayoutSpace};
let margins = self.style.page.margins();
- crate::layout::layout(
- &model,
+ layout(
+ &tree,
LayoutContext {
loader: &self.loader,
style: &self.style,
- base: self.style.page.dimensions.unpadded(margins),
+ base: self.style.page.size.unpadded(margins),
spaces: vec![LayoutSpace {
- dimensions: self.style.page.dimensions,
+ size: self.style.page.size,
padding: margins,
expansion: LayoutExpansion::new(true, true),
}],
@@ -121,6 +116,11 @@ impl Typesetter {
}
}
+/// 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>>;
+
/// The result of some pass: Some output `T` and feedback data.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Pass<T> {
diff --git a/src/library/font.rs b/src/library/font.rs
index a5ee7d9c..efcbb86f 100644
--- a/src/library/font.rs
+++ b/src/library/font.rs
@@ -1,3 +1,5 @@
+//! Font configuration.
+
use fontdock::{FontStyle, FontWeight, FontWidth};
use crate::length::ScaleLength;
use super::*;
@@ -6,7 +8,7 @@ function! {
/// `font`: Configure the font.
#[derive(Debug, Clone, PartialEq)]
pub struct FontFunc {
- body: Option<SyntaxModel>,
+ body: Option<SyntaxTree>,
size: Option<ScaleLength>,
style: Option<FontStyle>,
weight: Option<FontWeight>,
diff --git a/src/library/layout.rs b/src/library/layout.rs
index 5dd754bb..d46265a4 100644
--- a/src/library/layout.rs
+++ b/src/library/layout.rs
@@ -1,3 +1,5 @@
+//! Layout building blocks.
+
use crate::length::ScaleLength;
use super::*;
@@ -5,14 +7,14 @@ function! {
/// `box`: Layouts content into a box.
#[derive(Debug, Clone, PartialEq)]
pub struct BoxFunc {
- body: SyntaxModel,
+ body: SyntaxTree,
width: Option<ScaleLength>,
height: Option<ScaleLength>,
}
parse(header, body, ctx, f) {
BoxFunc {
- body: body!(opt: body, ctx, f).unwrap_or(SyntaxModel::new()),
+ body: body!(opt: body, ctx, f).unwrap_or(SyntaxTree::new()),
width: header.args.key.get::<ScaleLength>("width", f),
height: header.args.key.get::<ScaleLength>("height", f),
}
@@ -25,14 +27,14 @@ function! {
self.width.with(|v| {
let length = v.raw_scaled(ctx.base.x);
ctx.base.x = length;
- ctx.spaces[0].dimensions.x = length;
+ ctx.spaces[0].size.x = length;
ctx.spaces[0].expansion.horizontal = true;
});
self.height.with(|v| {
let length = v.raw_scaled(ctx.base.y);
ctx.base.y = length;
- ctx.spaces[0].dimensions.y = length;
+ ctx.spaces[0].size.y = length;
ctx.spaces[0].expansion.vertical = true;
});
@@ -48,7 +50,7 @@ function! {
/// `align`: Aligns content along the layouting axes.
#[derive(Debug, Clone, PartialEq)]
pub struct AlignFunc {
- body: Option<SyntaxModel>,
+ body: Option<SyntaxTree>,
aligns: Vec<Spanned<SpecAlign>>,
h: Option<Spanned<SpecAlign>>,
v: Option<Spanned<SpecAlign>>,
@@ -64,7 +66,7 @@ function! {
}
layout(self, ctx, f) {
- ctx.base = ctx.spaces[0].dimensions;
+ ctx.base = ctx.spaces[0].size;
let axes = ctx.axes;
let all = self.aligns.iter()
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 7b7034a0..7a664257 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -1,12 +1,14 @@
//! The _Typst_ standard library.
-use crate::syntax::scope::Scope;
use crate::func::prelude::*;
+use crate::layout::{LayoutContext, Commands};
+use crate::syntax::scope::Scope;
-pub_use_mod!(font);
-pub_use_mod!(layout);
-pub_use_mod!(page);
-pub_use_mod!(spacing);
+macro_rules! lib { ($name:ident) => { mod $name; pub use $name::*; }}
+lib!(font);
+lib!(layout);
+lib!(page);
+lib!(spacing);
/// Create a scope with all standard functions.
pub fn std() -> Scope {
@@ -17,10 +19,10 @@ pub fn std() -> Scope {
std.add::<PageFunc>("page");
std.add::<AlignFunc>("align");
std.add::<BoxFunc>("box");
- std.add_with_meta::<SpacingFunc>("h", Horizontal);
- std.add_with_meta::<SpacingFunc>("v", Vertical);
std.add::<ParBreakFunc>("parbreak");
std.add::<PageBreakFunc>("pagebreak");
+ std.add_with_meta::<SpacingFunc>("h", Horizontal);
+ std.add_with_meta::<SpacingFunc>("v", Vertical);
std
}
@@ -29,7 +31,7 @@ function! {
/// `val`: Layouts the body with no special effect.
#[derive(Debug, Clone, PartialEq)]
pub struct ValFunc {
- body: Option<SyntaxModel>,
+ body: Option<SyntaxTree>,
}
parse(header, body, state, f) {
@@ -40,7 +42,7 @@ function! {
layout(self, ctx, f) {
match &self.body {
- Some(model) => vec![LayoutSyntaxModel(model)],
+ Some(tree) => vec![LayoutSyntaxTree(tree)],
None => vec![],
}
}
@@ -48,7 +50,7 @@ function! {
/// Layout an optional body with a change of the text style.
fn styled<'a, T, F>(
- body: &'a Option<SyntaxModel>,
+ body: &'a Option<SyntaxTree>,
ctx: LayoutContext<'_>,
data: Option<T>,
f: F,
@@ -58,9 +60,9 @@ fn styled<'a, T, F>(
f(&mut style, data);
match body {
- Some(model) => vec![
+ Some(tree) => vec![
SetTextStyle(style),
- LayoutSyntaxModel(model),
+ LayoutSyntaxTree(tree),
SetTextStyle(ctx.style.text.clone()),
],
None => vec![SetTextStyle(style)],
diff --git a/src/library/page.rs b/src/library/page.rs
index f1dcc9bc..d1964fd2 100644
--- a/src/library/page.rs
+++ b/src/library/page.rs
@@ -1,3 +1,5 @@
+//! Page setup.
+
use crate::length::{Length, ScaleLength};
use crate::paper::{Paper, PaperClass};
use super::*;
@@ -37,13 +39,13 @@ function! {
if let Some(paper) = self.paper {
style.class = paper.class;
- style.dimensions = paper.size();
+ style.size = paper.size();
} else if self.width.is_some() || self.height.is_some() {
style.class = PaperClass::Custom;
}
- self.width.with(|v| style.dimensions.x = v.as_raw());
- self.height.with(|v| style.dimensions.y = v.as_raw());
+ self.width.with(|v| style.size.x = v.as_raw());
+ self.height.with(|v| style.size.y = v.as_raw());
self.margins.with(|v| style.margins.set_all(Some(v)));
self.left.with(|v| style.margins.left = Some(v));
self.right.with(|v| style.margins.right = Some(v));
@@ -51,7 +53,7 @@ function! {
self.bottom.with(|v| style.margins.bottom = Some(v));
if self.flip {
- style.dimensions.swap();
+ style.size.swap();
}
vec![SetPageStyle(style)]
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
index 68ef27f2..22c4669e 100644
--- a/src/library/spacing.rs
+++ b/src/library/spacing.rs
@@ -1,3 +1,5 @@
+//! Spacing.
+
use crate::length::ScaleLength;
use crate::layout::SpacingKind;
use super::*;
diff --git a/src/style.rs b/src/style.rs
index 0490ef07..9dce381e 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -97,7 +97,7 @@ pub struct PageStyle {
/// The class of this page.
pub class: PaperClass,
/// The width and height of the page.
- pub dimensions: Size,
+ pub size: Size,
/// The amount of white space on each side. If a side is set to `None`, the
/// default for the paper class is used.
pub margins: Value4<Option<ScaleLength>>,
@@ -108,21 +108,20 @@ impl PageStyle {
pub fn new(paper: Paper) -> PageStyle {
PageStyle {
class: paper.class,
- dimensions: paper.size(),
+ size: paper.size(),
margins: Value4::with_all(None),
}
}
/// The absolute margins.
pub fn margins(&self) -> Margins {
- let dims = self.dimensions;
+ let size = self.size;
let default = self.class.default_margins();
-
Margins {
- left: self.margins.left.unwrap_or(default.left).raw_scaled(dims.x),
- top: self.margins.top.unwrap_or(default.top).raw_scaled(dims.y),
- right: self.margins.right.unwrap_or(default.right).raw_scaled(dims.x),
- bottom: self.margins.bottom.unwrap_or(default.bottom).raw_scaled(dims.y),
+ left: self.margins.left.unwrap_or(default.left).raw_scaled(size.x),
+ top: self.margins.top.unwrap_or(default.top).raw_scaled(size.y),
+ right: self.margins.right.unwrap_or(default.right).raw_scaled(size.x),
+ bottom: self.margins.bottom.unwrap_or(default.bottom).raw_scaled(size.y),
}
}
}
diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs
index a551c2b6..0a9ab149 100644
--- a/src/syntax/expr.rs
+++ b/src/syntax/expr.rs
@@ -7,7 +7,7 @@ use std::u8;
use crate::Feedback;
use crate::length::Length;
-use super::span::Spanned;
+use super::span::{Spanned, SpanVec};
use super::tokens::is_identifier;
use super::value::Value;
@@ -237,7 +237,7 @@ impl fmt::Display for ParseColorError {
/// (false, 12cm, "hi")
/// ```
#[derive(Default, Clone, PartialEq)]
-pub struct Tuple(pub Vec<Spanned<Expr>>);
+pub struct Tuple(pub SpanVec<Expr>);
impl Tuple {
/// Create an empty tuple.
@@ -333,7 +333,7 @@ impl Deref for NamedTuple {
/// { fit: false, width: 12cm, items: (1, 2, 3) }
/// ```
#[derive(Default, Clone, PartialEq)]
-pub struct Object(pub Vec<Spanned<Pair>>);
+pub struct Object(pub SpanVec<Pair>);
/// A key-value pair in an object.
#[derive(Debug, Clone, PartialEq)]
diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs
index e844fdf1..86c2fd24 100644
--- a/src/syntax/mod.rs
+++ b/src/syntax/mod.rs
@@ -1,4 +1,4 @@
-//! Syntax models, parsing and tokenization.
+//! Syntax trees, parsing and tokenization.
#[cfg(test)]
#[macro_use]
@@ -6,7 +6,7 @@ mod test;
pub mod decoration;
pub mod expr;
-pub mod model;
+pub mod tree;
pub mod parsing;
pub mod span;
pub mod scope;
diff --git a/src/syntax/model.rs b/src/syntax/model.rs
deleted file mode 100644
index 4eb2abe0..00000000
--- a/src/syntax/model.rs
+++ /dev/null
@@ -1,134 +0,0 @@
-//! The syntax model.
-
-use std::any::Any;
-use std::fmt::Debug;
-use async_trait::async_trait;
-
-use crate::{Pass, Feedback};
-use crate::layout::{LayoutContext, Commands, Command};
-use super::span::{Spanned, SpanVec};
-
-/// Represents a parsed piece of source that can be layouted and in the future
-/// also be queried for information used for refactorings, autocomplete, etc.
-#[async_trait(?Send)]
-pub trait Model: Debug + ModelBounds {
- /// Layout the model into a sequence of commands processed by a
- /// [`ModelLayouter`](crate::layout::ModelLayouter).
- async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>>;
-}
-
-/// A tree representation of source code.
-#[derive(Debug, Default, Clone, PartialEq)]
-pub struct SyntaxModel {
- /// The syntactical elements making up this model.
- pub nodes: SpanVec<Node>,
-}
-
-impl SyntaxModel {
- /// Create an empty syntax model.
- pub fn new() -> SyntaxModel {
- SyntaxModel { nodes: vec![] }
- }
-
- /// Add a node to the model.
- pub fn add(&mut self, node: Spanned<Node>) {
- self.nodes.push(node);
- }
-}
-
-#[async_trait(?Send)]
-impl Model for SyntaxModel {
- async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> {
- Pass::new(vec![Command::LayoutSyntaxModel(self)], Feedback::new())
- }
-}
-
-/// A node in the [syntax model](SyntaxModel).
-#[derive(Debug, Clone)]
-pub enum Node {
- /// Whitespace containing less than two newlines.
- Space,
- /// Whitespace with more than two newlines.
- Parbreak,
- /// A forced line break.
- Linebreak,
- /// Plain text.
- Text(String),
- /// Lines of raw text.
- Raw(Vec<String>),
- /// Italics were enabled / disabled.
- ToggleItalic,
- /// Bolder was enabled / disabled.
- ToggleBolder,
- /// A submodel, typically a function invocation.
- Model(Box<dyn Model>),
-}
-
-impl PartialEq for Node {
- fn eq(&self, other: &Node) -> bool {
- use Node::*;
- match (self, other) {
- (Space, Space) => true,
- (Parbreak, Parbreak) => true,
- (Linebreak, Linebreak) => true,
- (Text(a), Text(b)) => a == b,
- (Raw(a), Raw(b)) => a == b,
- (ToggleItalic, ToggleItalic) => true,
- (ToggleBolder, ToggleBolder) => true,
- (Model(a), Model(b)) => a == b,
- _ => false,
- }
- }
-}
-
-impl dyn Model {
- /// Downcast this model to a concrete type implementing [`Model`].
- pub fn downcast<T>(&self) -> Option<&T> where T: Model + 'static {
- self.as_any().downcast_ref::<T>()
- }
-}
-
-impl PartialEq for dyn Model {
- fn eq(&self, other: &dyn Model) -> bool {
- self.bound_eq(other)
- }
-}
-
-impl Clone for Box<dyn Model> {
- fn clone(&self) -> Self {
- self.bound_clone()
- }
-}
-
-/// This trait describes bounds necessary for types implementing [`Model`]. It is
-/// automatically implemented for all types that are [`Model`], [`PartialEq`],
-/// [`Clone`] and `'static`.
-///
-/// It is necessary to make models comparable and clonable.
-pub trait ModelBounds {
- /// Convert into a `dyn Any`.
- fn as_any(&self) -> &dyn Any;
-
- /// Check for equality with another model.
- fn bound_eq(&self, other: &dyn Model) -> bool;
-
- /// Clone into a boxed model trait object.
- fn bound_clone(&self) -> Box<dyn Model>;
-}
-
-impl<T> ModelBounds for T where T: Model + PartialEq + Clone + 'static {
- fn as_any(&self) -> &dyn Any {
- self
- }
-
- fn bound_eq(&self, other: &dyn Model) -> bool {
- match other.as_any().downcast_ref::<Self>() {
- Some(other) => self == other,
- None => false,
- }
- }
-
- fn bound_clone(&self) -> Box<dyn Model> {
- Box::new(self.clone())
- }
-}
diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs
index 75e30177..7594c14d 100644
--- a/src/syntax/parsing.rs
+++ b/src/syntax/parsing.rs
@@ -1,4 +1,4 @@
-//! Parsing of source code into syntax models.
+//! Parsing of source code into syntax trees.
use std::str::FromStr;
@@ -8,10 +8,10 @@ use super::expr::*;
use super::scope::Scope;
use super::span::{Pos, Span, Spanned};
use super::tokens::{is_newline_char, Token, Tokens, TokenMode};
-use super::model::{SyntaxModel, Node, Model};
+use super::tree::{SyntaxTree, SyntaxNode, DynamicNode};
-/// A function which parses a function call into a model.
-pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<Box<dyn Model>>;
+/// A function which parses a function call into a tree.
+pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<Box<dyn DynamicNode>>;
/// An invocation of a function.
#[derive(Debug, Clone, PartialEq)]
@@ -73,12 +73,12 @@ pub struct ParseState {
/// Parse a string of source code.
///
-/// All spans in the resulting model and feedback are offset by the given
+/// All spans in the resulting tree and feedback are offset by the given
/// `offset` position. This is used to make spans of a function body relative to
/// the start of the function as a whole as opposed to the start of the
/// function's body.
-pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxModel> {
- let mut model = SyntaxModel::new();
+pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxTree> {
+ let mut tree = SyntaxTree::new();
let mut feedback = Feedback::new();
for token in Tokens::new(src, offset, TokenMode::Body) {
@@ -87,9 +87,9 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxModel> {
// Starting from two newlines counts as a paragraph break, a single
// newline does not.
Token::Space(newlines) => if newlines >= 2 {
- Node::Parbreak
+ SyntaxNode::Parbreak
} else {
- Node::Space
+ SyntaxNode::Space
}
Token::Function { header, body, terminated } => {
@@ -103,19 +103,19 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxModel> {
parsed.output
}
- Token::Star => Node::ToggleBolder,
- Token::Underscore => Node::ToggleItalic,
- Token::Backslash => Node::Linebreak,
+ Token::Star => SyntaxNode::ToggleBolder,
+ Token::Underscore => SyntaxNode::ToggleItalic,
+ Token::Backslash => SyntaxNode::Linebreak,
Token::Raw { raw, terminated } => {
if !terminated {
error!(@feedback, Span::at(span.end), "expected backtick");
}
- Node::Raw(unescape_raw(raw))
+ SyntaxNode::Raw(unescape_raw(raw))
}
- Token::Text(text) => Node::Text(text.to_string()),
+ Token::Text(text) => SyntaxNode::Text(text.to_string()),
Token::LineComment(_) | Token::BlockComment(_) => continue,
unexpected => {
@@ -124,10 +124,10 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxModel> {
}
};
- model.add(Spanned::new(node, span));
+ tree.push(Spanned::new(node, span));
}
- Pass::new(model, feedback)
+ Pass::new(tree, feedback)
}
struct FuncParser<'s> {
@@ -164,7 +164,7 @@ impl<'s> FuncParser<'s> {
}
}
- fn parse(mut self) -> Pass<Node> {
+ fn parse(mut self) -> Pass<SyntaxNode> {
let (parser, header) = if let Some(header) = self.parse_func_header() {
let name = header.name.v.as_str();
let (parser, deco) = match self.state.scope.get_parser(name) {
@@ -197,9 +197,8 @@ impl<'s> FuncParser<'s> {
let call = FuncCall { header, body: self.body };
let parsed = parser(call, self.state);
-
self.feedback.extend(parsed.feedback);
- Pass::new(Node::Model(parsed.output), self.feedback)
+ Pass::new(SyntaxNode::Dyn(parsed.output), self.feedback)
}
fn parse_func_header(&mut self) -> Option<FuncHeader> {
@@ -662,26 +661,27 @@ fn unescape_raw(raw: &str) -> Vec<String> {
#[allow(non_snake_case)]
mod tests {
use crate::length::Length;
- use super::super::test::{check, DebugFn};
+ use crate::syntax::span::SpanVec;
+ use crate::syntax::test::{check, DebugFn};
use super::*;
use Decoration::*;
use Expr::{Number as Num, Length as Len, Bool};
- use Node::{
+ use SyntaxNode::{
Space as S, ToggleItalic as Italic, ToggleBolder as Bold,
Parbreak, Linebreak,
};
/// Test whether the given string parses into
- /// - the given node list (required).
+ /// - the given SyntaxNode list (required).
/// - the given error list (optional, if omitted checks against empty list).
/// - the given decoration list (optional, if omitted it is not tested).
macro_rules! p {
- ($source:expr => [$($model:tt)*]) => {
- p!($source => [$($model)*], []);
+ ($source:expr => [$($tree:tt)*]) => {
+ p!($source => [$($tree)*], []);
};
- ($source:expr => [$($model:tt)*], [$($diagnostics:tt)*] $(, [$($decos:tt)*])? $(,)?) => {
+ ($source:expr => [$($tree:tt)*], [$($diagnostics:tt)*] $(, [$($decos:tt)*])? $(,)?) => {
let mut scope = Scope::new::<DebugFn>();
scope.add::<DebugFn>("f");
scope.add::<DebugFn>("n");
@@ -691,9 +691,9 @@ mod tests {
let state = ParseState { scope };
let pass = parse($source, Pos::ZERO, &state);
- // Test model.
- let (exp, cmp) = span_vec![$($model)*];
- check($source, exp, pass.output.nodes, cmp);
+ // Test tree.
+ let (exp, cmp) = span_vec![$($tree)*];
+ check($source, exp, pass.output, cmp);
// Test diagnostics.
let (exp, cmp) = span_vec![$($diagnostics)*];
@@ -728,7 +728,7 @@ mod tests {
fn Sub(e1: Expr, e2: Expr) -> Expr { Expr::Sub(Box::new(Z(e1)), Box::new(Z(e2))) }
fn Mul(e1: Expr, e2: Expr) -> Expr { Expr::Mul(Box::new(Z(e1)), Box::new(Z(e2))) }
fn Div(e1: Expr, e2: Expr) -> Expr { Expr::Div(Box::new(Z(e1)), Box::new(Z(e2))) }
- fn T(text: &str) -> Node { Node::Text(text.to_string()) }
+ fn T(text: &str) -> SyntaxNode { SyntaxNode::Text(text.to_string()) }
fn Z<T>(v: T) -> Spanned<T> { Spanned::zero(v) }
macro_rules! tuple {
@@ -757,7 +757,7 @@ mod tests {
macro_rules! raw {
($($line:expr),* $(,)?) => {
- Node::Raw(vec![$($line.to_string()),*])
+ SyntaxNode::Raw(vec![$($line.to_string()),*])
};
}
@@ -769,7 +769,7 @@ mod tests {
#[allow(unused_mut)]
let mut args = FuncArgs::new();
$(
- let items: Vec<Spanned<Expr>> = span_vec![$($pos)*].0;
+ let items: SpanVec<Expr> = span_vec![$($pos)*].0;
for item in items {
args.push(item.map(|v| FuncArg::Pos(v)));
}
@@ -778,7 +778,7 @@ mod tests {
value: Z($value),
})));)*)?
)?
- Node::Model(Box::new(DebugFn {
+ SyntaxNode::Dyn(Box::new(DebugFn {
header: FuncHeader {
name: span_item!($name).map(|s| Ident(s.to_string())),
args,
@@ -786,7 +786,7 @@ mod tests {
body: func!(@body $($($body)*)?),
}))
}};
- (@body [$($body:tt)*]) => { Some(SyntaxModel { nodes: span_vec![$($body)*].0 }) };
+ (@body [$($body:tt)*]) => { Some(span_vec![$($body)*].0) };
(@body) => { None };
}
@@ -818,8 +818,8 @@ mod tests {
#[test]
fn unescape_raws() {
- fn test(raw: &str, expected: Node) {
- let vec = if let Node::Raw(v) = expected { v } else { panic!() };
+ fn test(raw: &str, expected: SyntaxNode) {
+ let vec = if let SyntaxNode::Raw(v) = expected { v } else { panic!() };
assert_eq!(unescape_raw(raw), vec);
}
@@ -834,8 +834,8 @@ mod tests {
}
#[test]
- fn parse_basic_nodes() {
- // Basic nodes.
+ fn parse_basic_SyntaxNodes() {
+ // Basic SyntaxNodes.
p!("" => []);
p!("hi" => [T("hi")]);
p!("*hi" => [Bold, T("hi")]);
@@ -855,7 +855,7 @@ mod tests {
p!("`hi\nyou" => [raw!["hi", "you"]], [(1:3, 1:3, "expected backtick")]);
p!("`hi\\`du`" => [raw!["hi`du"]]);
- // Spanned nodes.
+ // Spanned SyntaxNodes.
p!("Hi" => [(0:0, 0:2, T("Hi"))]);
p!("*Hi*" => [(0:0, 0:1, Bold), (0:1, 0:3, T("Hi")), (0:3, 0:4, Bold)]);
p!("🌎\n*/[n]" =>
diff --git a/src/syntax/scope.rs b/src/syntax/scope.rs
index 83b9fdee..d3092944 100644
--- a/src/syntax/scope.rs
+++ b/src/syntax/scope.rs
@@ -5,7 +5,7 @@ use std::fmt::{self, Debug, Formatter};
use crate::func::ParseFunc;
use super::parsing::CallParser;
-use super::model::Model;
+use super::tree::DynamicNode;
/// A map from identifiers to function parsers.
pub struct Scope {
@@ -17,7 +17,7 @@ impl Scope {
/// Create a new empty scope with a fallback parser that is invoked when no
/// match is found.
pub fn new<F>() -> Scope
- where F: ParseFunc<Meta=()> + Model + 'static {
+ where F: ParseFunc<Meta=()> + DynamicNode + 'static {
Scope {
parsers: HashMap::new(),
fallback: make_parser::<F>(()),
@@ -31,14 +31,14 @@ impl Scope {
/// Associate the given name with a type that is parseable into a function.
pub fn add<F>(&mut self, name: &str)
- where F: ParseFunc<Meta=()> + Model + 'static {
+ where F: ParseFunc<Meta=()> + DynamicNode + 'static {
self.add_with_meta::<F>(name, ());
}
/// Add a parseable type with additional metadata that is given to the
/// parser (other than the default of `()`).
pub fn add_with_meta<F>(&mut self, name: &str, metadata: <F as ParseFunc>::Meta)
- where F: ParseFunc + Model + 'static {
+ where F: ParseFunc + DynamicNode + 'static {
self.parsers.insert(
name.to_string(),
make_parser::<F>(metadata),
@@ -65,9 +65,9 @@ impl Debug for Scope {
}
fn make_parser<F>(metadata: <F as ParseFunc>::Meta) -> Box<CallParser>
-where F: ParseFunc + Model + 'static {
+where F: ParseFunc + DynamicNode + 'static {
Box::new(move |f, s| {
F::parse(f, s, metadata.clone())
- .map(|model| Box::new(model) as Box<dyn Model>)
+ .map(|tree| Box::new(tree) as Box<dyn DynamicNode>)
})
}
diff --git a/src/syntax/test.rs b/src/syntax/test.rs
index 38a124aa..b701e577 100644
--- a/src/syntax/test.rs
+++ b/src/syntax/test.rs
@@ -5,7 +5,7 @@ use super::expr::{Expr, Ident, Tuple, NamedTuple, Object, Pair};
use super::parsing::{FuncHeader, FuncArgs, FuncArg};
use super::span::Spanned;
use super::tokens::Token;
-use super::model::{SyntaxModel, Model, Node};
+use super::tree::{SyntaxTree, SyntaxNode, DynamicNode};
/// Check whether the expected and found results are the same.
pub fn check<T>(src: &str, exp: T, found: T, cmp_spans: bool)
@@ -62,7 +62,7 @@ function! {
#[derive(Debug, Clone, PartialEq)]
pub struct DebugFn {
pub header: FuncHeader,
- pub body: Option<SyntaxModel>,
+ pub body: Option<SyntaxTree>,
}
parse(header, body, state, f) {
@@ -83,20 +83,14 @@ pub trait SpanlessEq<Rhs=Self> {
fn spanless_eq(&self, other: &Rhs) -> bool;
}
-impl SpanlessEq for SyntaxModel {
- fn spanless_eq(&self, other: &SyntaxModel) -> bool {
- self.nodes.spanless_eq(&other.nodes)
- }
-}
-
-impl SpanlessEq for Node {
- fn spanless_eq(&self, other: &Node) -> bool {
- fn downcast<'a>(func: &'a (dyn Model + 'static)) -> &'a DebugFn {
+impl SpanlessEq for SyntaxNode {
+ fn spanless_eq(&self, other: &SyntaxNode) -> bool {
+ fn downcast<'a>(func: &'a (dyn DynamicNode + 'static)) -> &'a DebugFn {
func.downcast::<DebugFn>().expect("not a debug fn")
}
match (self, other) {
- (Node::Model(a), Node::Model(b)) => {
+ (SyntaxNode::Dyn(a), SyntaxNode::Dyn(b)) => {
downcast(a.as_ref()).spanless_eq(downcast(b.as_ref()))
}
(a, b) => a == b,
diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs
index a0af5a02..1ea11449 100644
--- a/src/syntax/tokens.rs
+++ b/src/syntax/tokens.rs
@@ -83,8 +83,8 @@ pub enum Token<'s> {
ExprHex(&'s str),
/// A plus in a function header, signifying the addition of expressions.
Plus,
- /// A hyphen in a function header,
- /// signifying the subtraction of expressions.
+ /// A hyphen in a function header, signifying the subtraction of
+ /// expressions.
Hyphen,
/// A slash in a function header, signifying the division of expressions.
Slash,
diff --git a/src/syntax/tree.rs b/src/syntax/tree.rs
new file mode 100644
index 00000000..41a03fae
--- /dev/null
+++ b/src/syntax/tree.rs
@@ -0,0 +1,100 @@
+//! The syntax tree.
+
+use std::any::Any;
+use std::fmt::Debug;
+
+use crate::layout::Layout;
+use super::span::SpanVec;
+
+/// A list of nodes which forms a tree together with the nodes' children.
+pub type SyntaxTree = SpanVec<SyntaxNode>;
+
+/// A syntax node, which encompasses a single logical entity of parsed source
+/// code.
+#[derive(Debug, Clone)]
+pub enum SyntaxNode {
+ /// Whitespace containing less than two newlines.
+ Space,
+ /// Whitespace with more than two newlines.
+ Parbreak,
+ /// A forced line break.
+ Linebreak,
+ /// Plain text.
+ Text(String),
+ /// Lines of raw text.
+ Raw(Vec<String>),
+ /// Italics were enabled / disabled.
+ ToggleItalic,
+ /// Bolder was enabled / disabled.
+ ToggleBolder,
+ /// A subtree, typically a function invocation.
+ Dyn(Box<dyn DynamicNode>),
+}
+
+impl PartialEq for SyntaxNode {
+ fn eq(&self, other: &SyntaxNode) -> bool {
+ use SyntaxNode::*;
+ match (self, other) {
+ (Space, Space) => true,
+ (Parbreak, Parbreak) => true,
+ (Linebreak, Linebreak) => true,
+ (Text(a), Text(b)) => a == b,
+ (Raw(a), Raw(b)) => a == b,
+ (ToggleItalic, ToggleItalic) => true,
+ (ToggleBolder, ToggleBolder) => true,
+ (Dyn(a), Dyn(b)) => a == b,
+ _ => false,
+ }
+ }
+}
+
+/// Dynamic syntax nodes.
+///
+/// *Note*: This is automatically implemented for all types which are
+/// `Debug + Clone + PartialEq`, `Layout` and `'static`.
+pub trait DynamicNode: Debug + Layout {
+ /// Convert into a `dyn Any`.
+ fn as_any(&self) -> &dyn Any;
+
+ /// Check for equality with another dynamic node.
+ fn dyn_eq(&self, other: &dyn DynamicNode) -> bool;
+
+ /// Clone into a boxed node trait object.
+ fn box_clone(&self) -> Box<dyn DynamicNode>;
+}
+
+impl dyn DynamicNode {
+ /// Downcast this dynamic node to a concrete node.
+ pub fn downcast<N>(&self) -> Option<&N> where N: DynamicNode + 'static {
+ self.as_any().downcast_ref::<N>()
+ }
+}
+
+impl PartialEq for dyn DynamicNode {
+ fn eq(&self, other: &dyn DynamicNode) -> bool {
+ self.dyn_eq(other)
+ }
+}
+
+impl Clone for Box<dyn DynamicNode> {
+ fn clone(&self) -> Self {
+ self.box_clone()
+ }
+}
+
+impl<T> DynamicNode for T where T: Debug + PartialEq + Clone + Layout + 'static {
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+
+ fn dyn_eq(&self, other: &dyn DynamicNode) -> bool {
+ match other.as_any().downcast_ref::<Self>() {
+ Some(other) => self == other,
+ None => false,
+ }
+ }
+
+ fn box_clone(&self) -> Box<dyn DynamicNode> {
+ Box::new(self.clone())
+ }
+}
diff --git a/tests/test_typeset.rs b/tests/test_typeset.rs
index 216e4a1a..6ac34c85 100644
--- a/tests/test_typeset.rs
+++ b/tests/test_typeset.rs
@@ -73,7 +73,7 @@ fn main() {
typesetter.set_page_style(PageStyle {
class: PaperClass::Custom,
- dimensions: Size::with_all(Length::pt(250.0).as_raw()),
+ size: Size::with_all(Length::pt(250.0).as_raw()),
margins: Value4::with_all(None),
});
@@ -148,13 +148,13 @@ fn render(
) -> DrawTarget {
let pad = scale * 10.0;
let width = 2.0 * pad + layouts.iter()
- .map(|l| scale * l.dimensions.x)
+ .map(|l| scale * l.size.x)
.max_by(|a, b| a.partial_cmp(&b).unwrap())
.unwrap()
.round();
let height = pad + layouts.iter()
- .map(|l| scale * l.dimensions.y + pad)
+ .map(|l| scale * l.size.y + pad)
.sum::<f64>()
.round();
@@ -166,8 +166,8 @@ fn render(
surface.fill_rect(
offset.x as f32,
offset.y as f32,
- (scale * layout.dimensions.x) as f32,
- (scale * layout.dimensions.y) as f32,
+ (scale * layout.size.x) as f32,
+ (scale * layout.size.y) as f32,
&Source::Solid(WHITE),
&Default::default(),
);
@@ -186,7 +186,7 @@ fn render(
}
}
- offset.y += scale * layout.dimensions.y + pad;
+ offset.y += scale * layout.size.y + pad;
}
surface