summaryrefslogtreecommitdiff
path: root/src/layout/model.rs
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/model.rs
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/model.rs')
-rw-r--r--src/layout/model.rs193
1 files changed, 193 insertions, 0 deletions
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,
+ }
+ }
+}