summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-10-13 12:08:07 +0200
committerLaurenz <laurmaedje@gmail.com>2019-10-13 12:08:07 +0200
commit463e4ebd8234da5e28700e9b22b6ef5f0dfef56f (patch)
tree01f3961d6996de3ee8f9819c2792f6ee8c2a3c3d /src
parent6f22e4f13c42f06b686a01fbdd28a0163e88ae77 (diff)
Refactor layout types 🚧
Diffstat (limited to 'src')
-rw-r--r--src/doc.rs23
-rw-r--r--src/export/pdf.rs43
-rw-r--r--src/func.rs50
-rw-r--r--src/layout/actions.rs4
-rw-r--r--src/layout/flex.rs140
-rw-r--r--src/layout/mod.rs179
-rw-r--r--src/layout/stacked.rs (renamed from src/layout/boxed.rs)99
-rw-r--r--src/layout/text.rs4
-rw-r--r--src/lib.rs42
-rw-r--r--src/library/align.rs11
-rw-r--r--src/library/mod.rs7
-rw-r--r--src/library/styles.rs25
-rw-r--r--src/parsing/mod.rs12
13 files changed, 330 insertions, 309 deletions
diff --git a/src/doc.rs b/src/doc.rs
deleted file mode 100644
index 9a287913..00000000
--- a/src/doc.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-//! Representation of typesetted documents.
-
-use crate::layout::LayoutAction;
-use crate::size::Size;
-
-
-/// A complete typesetted document, which can be exported.
-#[derive(Debug, Clone)]
-pub struct Document {
- /// The pages of the document.
- pub pages: Vec<Page>,
-}
-
-/// A page of a document.
-#[derive(Debug, Clone)]
-pub struct Page {
- /// The width of the page.
- pub width: Size,
- /// The height of the page.
- pub height: Size,
- /// Layouting actions specifying how to draw content on the page.
- pub actions: Vec<LayoutAction>,
-}
diff --git a/src/export/pdf.rs b/src/export/pdf.rs
index 3a3f2a9c..78e48e81 100644
--- a/src/export/pdf.rs
+++ b/src/export/pdf.rs
@@ -14,12 +14,11 @@ use toddle::font::OwnedFont;
use toddle::query::SharedFontLoader;
use toddle::Error as FontError;
-use crate::layout::LayoutAction;
-use crate::doc::{Document, Page as DocPage};
+use crate::layout::{MultiLayout, Layout, LayoutAction};
use crate::size::{Size, Size2D};
-/// Exports documents into _PDFs_.
+/// Exports layouts into _PDFs_.
#[derive(Debug)]
pub struct PdfExporter {}
@@ -30,19 +29,19 @@ impl PdfExporter {
PdfExporter {}
}
- /// Export a typesetted document into a writer. Returns how many bytes were written.
+ /// Export a finished layouts into a writer. Returns how many bytes were written.
#[inline]
- pub fn export<W: Write>(&self, document: &Document, loader: &SharedFontLoader, target: W)
+ pub fn export<W: Write>(&self, layout: &MultiLayout, loader: &SharedFontLoader, target: W)
-> PdfResult<usize> {
- let mut engine = PdfEngine::new(document, loader, target)?;
+ let mut engine = PdfEngine::new(layout, loader, target)?;
engine.write()
}
}
-/// Writes documents in the _PDF_ format.
+/// Writes layouts in the _PDF_ format.
struct PdfEngine<'d, W: Write> {
writer: PdfWriter<W>,
- doc: &'d Document,
+ layout: &'d MultiLayout,
offsets: Offsets,
font_remap: HashMap<usize, usize>,
fonts: Vec<OwnedFont>,
@@ -60,16 +59,17 @@ struct Offsets {
impl<'d, W: Write> PdfEngine<'d, W> {
/// Create a new _PDF_ engine.
- fn new(doc: &'d Document, loader: &SharedFontLoader, target: W) -> PdfResult<PdfEngine<'d, W>> {
- // Create a subsetted PDF font for each font in the document.
+ fn new(layout: &'d MultiLayout, loader: &SharedFontLoader, target: W)
+ -> PdfResult<PdfEngine<'d, W>> {
+ // Create a subsetted PDF font for each font in the layout.
let mut font_remap = HashMap::new();
let fonts = {
let mut font = 0usize;
let mut chars = HashMap::new();
// Find out which characters are used for each font.
- for page in &doc.pages {
- for action in &page.actions {
+ for boxed in &layout.layouts {
+ for action in &boxed.actions {
match action {
LayoutAction::WriteText(string) => {
chars.entry(font)
@@ -108,21 +108,21 @@ impl<'d, W: Write> PdfEngine<'d, W> {
// Calculate a unique id for all objects that will be written.
let catalog = 1;
let page_tree = catalog + 1;
- let pages = (page_tree + 1, page_tree + doc.pages.len() as Ref);
- let contents = (pages.1 + 1, pages.1 + doc.pages.len() as Ref);
+ let pages = (page_tree + 1, page_tree + layout.layouts.len() as Ref);
+ let contents = (pages.1 + 1, pages.1 + layout.layouts.len() as Ref);
let font_offsets = (contents.1 + 1, contents.1 + 5 * fonts.len() as Ref);
let offsets = Offsets { catalog, page_tree, pages, contents, fonts: font_offsets };
Ok(PdfEngine {
writer: PdfWriter::new(target),
- doc,
+ layout,
offsets,
font_remap,
fonts,
})
}
- /// Write the complete document.
+ /// Write the complete layout.
fn write(&mut self) -> PdfResult<usize> {
self.writer.write_header(Version::new(1, 7))?;
self.write_page_tree()?;
@@ -150,9 +150,10 @@ impl<'d, W: Write> PdfEngine<'d, W> {
)?;
// The page objects
- for (id, page) in ids(self.offsets.pages).zip(&self.doc.pages) {
+ for (id, page) in ids(self.offsets.pages).zip(&self.layout.layouts) {
+ let rect = Rect::new(0.0, 0.0, page.dimensions.x.to_pt(), page.dimensions.y.to_pt());
self.writer.write_obj(id, Page::new(self.offsets.page_tree)
- .media_box(Rect::new(0.0, 0.0, page.width.to_pt(), page.height.to_pt()))
+ .media_box(rect)
.contents(ids(self.offsets.contents))
)?;
}
@@ -162,14 +163,14 @@ impl<'d, W: Write> PdfEngine<'d, W> {
/// Write the contents of all pages.
fn write_pages(&mut self) -> PdfResult<()> {
- for (id, page) in ids(self.offsets.contents).zip(&self.doc.pages) {
+ for (id, page) in ids(self.offsets.contents).zip(&self.layout.layouts) {
self.write_page(id, &page)?;
}
Ok(())
}
/// Write the content of a page.
- fn write_page(&mut self, id: u32, page: &DocPage) -> PdfResult<()> {
+ fn write_page(&mut self, id: u32, page: &Layout) -> PdfResult<()> {
let mut text = Text::new();
let mut active_font = (std::usize::MAX, 0.0);
@@ -195,7 +196,7 @@ impl<'d, W: Write> PdfEngine<'d, W> {
// Flush the position.
if let Some(pos) = next_pos.take() {
let x = pos.x.to_pt();
- let y = (page.height - pos.y - Size::pt(active_font.1)).to_pt();
+ let y = (page.dimensions.y - pos.y - Size::pt(active_font.1)).to_pt();
text.tm(1.0, 0.0, 0.0, 1.0, x, y);
}
diff --git a/src/func.rs b/src/func.rs
index 776f0e98..a7044b6e 100644
--- a/src/func.rs
+++ b/src/func.rs
@@ -4,9 +4,10 @@ use std::any::Any;
use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
-use crate::layout::{Layout, LayoutContext, LayoutResult};
+use toddle::query::FontClass;
+use crate::layout::{Layout, MultiLayout , LayoutContext, LayoutResult};
use crate::parsing::{ParseContext, ParseResult};
-use crate::syntax::FuncHeader;
+use crate::syntax::{SyntaxTree, FuncHeader};
/// Typesetting function types.
@@ -25,7 +26,7 @@ pub trait Function: FunctionBounds {
///
/// Returns optionally the resulting layout and a new context if changes to the context should
/// be made.
- fn layout(&self, ctx: LayoutContext) -> LayoutResult<Option<Layout>>;
+ fn layout(&self, ctx: LayoutContext) -> LayoutResult<FuncCommands>;
}
impl PartialEq for dyn Function {
@@ -34,6 +35,49 @@ impl PartialEq for dyn Function {
}
}
+/// A sequence of commands requested for execution by a function.
+#[derive(Debug)]
+pub struct FuncCommands {
+ pub commands: Vec<Command>
+}
+
+impl FuncCommands {
+ /// Create an empty command list.
+ pub fn new() -> FuncCommands {
+ FuncCommands {
+ commands: vec![],
+ }
+ }
+
+ /// Add a command to the sequence.
+ pub fn add_command(&mut self, command: Command) {
+ self.commands.push(command);
+ }
+
+ /// Whether there are any commands in this sequence.
+ pub fn is_empty(&self) -> bool {
+ self.commands.is_empty()
+ }
+}
+
+impl IntoIterator for FuncCommands {
+ type Item = Command;
+ type IntoIter = std::vec::IntoIter<Command>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.commands.into_iter()
+ }
+}
+
+/// Commands requested for execution by functions.
+#[derive(Debug)]
+pub enum Command {
+ Layout(SyntaxTree),
+ Add(Layout),
+ AddMany(MultiLayout),
+ ToggleStyleClass(FontClass),
+}
+
/// A helper trait that describes requirements for types that can implement [`Function`].
///
/// Automatically implemented for all types which fulfill to the bounds `Debug + PartialEq +
diff --git a/src/layout/actions.rs b/src/layout/actions.rs
index f76b61c3..3eb85e50 100644
--- a/src/layout/actions.rs
+++ b/src/layout/actions.rs
@@ -3,7 +3,7 @@
use std::fmt::{self, Display, Formatter};
use std::io::{self, Write};
use crate::size::Size2D;
-use super::boxed::BoxLayout;
+use super::Layout;
use LayoutAction::*;
@@ -104,7 +104,7 @@ impl LayoutActionList {
/// Add all actions from a box layout at a position. A move to the position
/// is generated and all moves inside the box layout are translated as necessary.
- pub fn add_box(&mut self, position: Size2D, layout: BoxLayout) {
+ pub fn add_box(&mut self, position: Size2D, layout: Layout) {
if let Some(target) = self.next_pos.take() {
self.actions.push(MoveAbsolute(target));
}
diff --git a/src/layout/flex.rs b/src/layout/flex.rs
index 75674e8f..68d39004 100644
--- a/src/layout/flex.rs
+++ b/src/layout/flex.rs
@@ -4,109 +4,85 @@ use crate::size::{Size, Size2D};
use super::*;
-/// A flex layout consists of a yet unarranged list of boxes.
-#[derive(Debug, Clone)]
-pub struct FlexLayout {
- /// The sublayouts composing this layout.
- pub units: Vec<FlexUnit>,
+
+/// Finishes a flex layout by justifying the positions of the individual boxes.
+#[derive(Debug)]
+pub struct FlexLayouter {
+ ctx: FlexContext,
+ units: Vec<FlexUnit>,
+
+ actions: LayoutActionList,
+ dimensions: Size2D,
+ usable: Size2D,
+ cursor: Size2D,
+
+ line_content: Vec<(Size2D, Layout)>,
+ line_metrics: Size2D,
+ last_glue: Option<Layout>,
+}
+
+/// The context for flex layouting.
+#[derive(Debug, Copy, Clone)]
+pub struct FlexContext {
+ /// The space to layout the boxes in.
+ pub space: LayoutSpace,
+ /// The flex spacing between two lines of boxes.
+ pub flex_spacing: Size,
}
/// A unit in a flex layout.
#[derive(Debug, Clone)]
-pub enum FlexUnit {
+enum FlexUnit {
/// A content unit to be arranged flexibly.
- Boxed(BoxLayout),
+ Boxed(Layout),
/// A unit which acts as glue between two [`FlexUnit::Boxed`] units and
/// is only present if there was no flow break in between the two surrounding boxes.
- Glue(BoxLayout),
+ Glue(Layout),
}
-impl FlexLayout {
- /// Create a new flex layout.
- pub fn new() -> FlexLayout {
- FlexLayout {
+impl FlexLayouter {
+ /// Create a new flex layouter.
+ pub fn new(ctx: FlexContext) -> FlexLayouter {
+ FlexLayouter {
+ ctx,
units: vec![],
+
+ actions: LayoutActionList::new(),
+ dimensions: match ctx.space.alignment {
+ Alignment::Left => Size2D::zero(),
+ Alignment::Right => Size2D::with_x(ctx.space.usable().x),
+ },
+ usable: ctx.space.usable(),
+ cursor: Size2D::new(ctx.space.padding.left, ctx.space.padding.top),
+
+ line_content: vec![],
+ line_metrics: Size2D::zero(),
+ last_glue: None,
}
}
- /// Create a new flex layout containing just one box.
- pub fn from_box(boxed: BoxLayout) -> FlexLayout {
- FlexLayout {
- units: vec![FlexUnit::Boxed(boxed)],
- }
+ /// Get a reference to this layouter's context.
+ pub fn ctx(&self) -> &FlexContext {
+ &self.ctx
}
/// Add a sublayout.
- pub fn add_box(&mut self, layout: BoxLayout) {
+ pub fn add(&mut self, layout: Layout) {
self.units.push(FlexUnit::Boxed(layout));
}
/// Add a glue layout which can be replaced by a line break.
- pub fn add_glue(&mut self, glue: BoxLayout) {
+ pub fn add_glue(&mut self, glue: Layout) {
self.units.push(FlexUnit::Glue(glue));
}
- /// Add all sublayouts of another flex layout.
- pub fn add_flexible(&mut self, layout: FlexLayout) {
- self.units.extend(layout.units);
- }
-
/// Whether this layouter contains any items.
pub fn is_empty(&self) -> bool {
self.units.is_empty()
}
/// Compute the justified layout.
- pub fn finish(self, ctx: FlexContext) -> LayoutResult<BoxLayout> {
- FlexFinisher::new(self, ctx).finish()
- }
-}
-
-/// The context for flex layouting.
-#[derive(Debug, Copy, Clone)]
-pub struct FlexContext {
- /// The space to layout the boxes in.
- pub space: LayoutSpace,
- /// The flex spacing between two lines of boxes.
- pub flex_spacing: Size,
-}
-
-/// Finishes a flex layout by justifying the positions of the individual boxes.
-#[derive(Debug)]
-struct FlexFinisher {
- units: Vec<FlexUnit>,
- ctx: FlexContext,
- actions: LayoutActionList,
- dimensions: Size2D,
- usable: Size2D,
- cursor: Size2D,
- line_metrics: Size2D,
- line_content: Vec<(Size2D, BoxLayout)>,
- glue: Option<BoxLayout>,
-}
-
-impl FlexFinisher {
- /// Create the finisher from the layout.
- fn new(layout: FlexLayout, ctx: FlexContext) -> FlexFinisher {
- let space = ctx.space;
- FlexFinisher {
- units: layout.units,
- ctx,
- actions: LayoutActionList::new(),
- dimensions: match ctx.space.alignment {
- Alignment::Left => Size2D::zero(),
- Alignment::Right => Size2D::with_x(space.usable().x),
- },
- usable: space.usable(),
- cursor: Size2D::new(space.padding.left, space.padding.top),
- line_metrics: Size2D::zero(),
- line_content: vec![],
- glue: None,
- }
- }
-
- /// Finish the flex layout into the justified box layout.
- fn finish(mut self) -> LayoutResult<BoxLayout> {
+ pub fn finish(mut self) -> LayoutResult<Layout> {
// Move the units out of the layout.
let units = self.units;
self.units = vec![];
@@ -122,7 +98,7 @@ impl FlexFinisher {
// Flush everything to get the correct dimensions.
self.newline();
- Ok(BoxLayout {
+ Ok(Layout {
dimensions: if self.ctx.space.shrink_to_fit {
self.dimensions.padded(self.ctx.space.padding)
} else {
@@ -134,8 +110,8 @@ impl FlexFinisher {
}
/// Layout the box.
- fn boxed(&mut self, boxed: BoxLayout) -> LayoutResult<()> {
- let last_glue_x = self.glue.as_ref()
+ fn boxed(&mut self, boxed: Layout) -> LayoutResult<()> {
+ let last_glue_x = self.last_glue.as_ref()
.map(|g| g.dimensions.x)
.unwrap_or(Size::zero());
@@ -147,7 +123,7 @@ impl FlexFinisher {
}
self.newline();
- } else if let Some(glue) = self.glue.take() {
+ } else if let Some(glue) = self.last_glue.take() {
self.append(glue);
}
@@ -157,15 +133,15 @@ impl FlexFinisher {
}
/// Layout the glue.
- fn glue(&mut self, glue: BoxLayout) {
- if let Some(glue) = self.glue.take() {
+ fn glue(&mut self, glue: Layout) {
+ if let Some(glue) = self.last_glue.take() {
self.append(glue);
}
- self.glue = Some(glue);
+ self.last_glue = Some(glue);
}
/// Append a box to the layout without checking anything.
- fn append(&mut self, layout: BoxLayout) {
+ fn append(&mut self, layout: Layout) {
let dim = layout.dimensions;
self.line_content.push((self.cursor, layout));
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 5cc8d8f2..19543389 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -1,50 +1,93 @@
//! The layouting engine.
use std::borrow::Cow;
+use std::io::{self, Write};
use std::mem;
use toddle::query::{SharedFontLoader, FontClass};
use toddle::Error as FontError;
+use crate::func::Command;
use crate::size::{Size, Size2D, SizeBox};
use crate::syntax::{SyntaxTree, Node, FuncCall};
use crate::style::TextStyle;
-use self::flex::{FlexLayout, FlexContext};
-use self::boxed::{BoxLayout, BoxContext, BoxLayouter};
-use self::text::TextContext;
-pub mod text;
-pub mod boxed;
-pub mod flex;
+mod text;
+mod stacked;
+mod flex;
mod actions;
pub use actions::{LayoutAction, LayoutActionList};
+pub use text::{layout_text, TextContext};
+pub use flex::{FlexLayouter, FlexContext};
+pub use stacked::{StackLayouter, StackContext};
-/// A collection of layouted content.
+/// A box layout has a fixed width and height and composes of actions.
#[derive(Debug, Clone)]
-pub enum Layout {
- /// A box layout.
- Boxed(BoxLayout),
- /// A flexible layout.
- Flex(FlexLayout),
+pub struct Layout {
+ /// The size of the box.
+ pub dimensions: Size2D,
+ /// The actions composing this layout.
+ pub actions: Vec<LayoutAction>,
+ /// Whether to debug-render this box.
+ pub debug_render: bool,
}
-/// Layout a syntax tree in a given context.
-pub fn layout(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<BoxLayout> {
- Layouter::new(tree, ctx).layout()
+impl Layout {
+ /// Serialize this layout into an output buffer.
+ pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
+ writeln!(f, "{:.4} {:.4}", self.dimensions.x.to_pt(), self.dimensions.y.to_pt())?;
+ for action in &self.actions {
+ action.serialize(f)?;
+ writeln!(f)?;
+ }
+ Ok(())
+ }
+}
+
+/// A collection of box layouts.
+#[derive(Debug, Clone)]
+pub struct MultiLayout {
+ pub layouts: Vec<Layout>,
+}
+
+impl MultiLayout {
+ /// Create an empty multibox layout.
+ pub fn new() -> MultiLayout {
+ MultiLayout {
+ layouts: vec![],
+ }
+ }
+
+ /// Extract a single sublayout and panic if this layout does not have
+ /// exactly one child.
+ pub fn into_single(mut self) -> Layout {
+ if self.layouts.len() != 1 {
+ panic!("into_single: contains not exactly one layout");
+ }
+ self.layouts.pop().unwrap()
+ }
+
+ /// Add a sublayout.
+ pub fn add(&mut self, layout: Layout) {
+ self.layouts.push(layout);
+ }
+
+ /// Whether this layout contains any sublayouts.
+ pub fn is_empty(&self) -> bool {
+ self.layouts.is_empty()
+ }
}
/// The context for layouting.
#[derive(Copy, Clone)]
pub struct LayoutContext<'a, 'p> {
- /// Loads fonts matching queries.
pub loader: &'a SharedFontLoader<'p>,
- /// Base style to set text with.
pub style: &'a TextStyle,
- /// The space to layout in.
pub space: LayoutSpace,
+ pub extra_space: Option<LayoutSpace>,
}
/// Spacial constraints for layouting.
@@ -61,13 +104,6 @@ pub struct LayoutSpace {
pub shrink_to_fit: bool,
}
-/// Where to align content.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub enum Alignment {
- Left,
- Right,
-}
-
impl LayoutSpace {
/// The actually usable area.
pub fn usable(&self) -> Size2D {
@@ -78,12 +114,23 @@ impl LayoutSpace {
}
}
+/// Where to align content.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum Alignment {
+ Left,
+ Right,
+}
+
+pub fn layout_tree(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiLayout> {
+ Layouter::new(tree, ctx).layout()
+}
+
/// Transforms a syntax tree into a box layout.
struct Layouter<'a, 'p> {
+ ctx: LayoutContext<'a, 'p>,
tree: &'a SyntaxTree,
- box_layouter: BoxLayouter,
- flex_layout: FlexLayout,
- loader: &'a SharedFontLoader<'p>,
+ stack_layouter: StackLayouter,
+ flex_layouter: FlexLayouter,
style: Cow<'a, TextStyle>,
}
@@ -91,16 +138,24 @@ impl<'a, 'p> Layouter<'a, 'p> {
/// Create a new layouter.
fn new(tree: &'a SyntaxTree, ctx: LayoutContext<'a, 'p>) -> Layouter<'a, 'p> {
Layouter {
+ ctx,
tree,
- box_layouter: BoxLayouter::new(BoxContext { space: ctx.space }),
- flex_layout: FlexLayout::new(),
- loader: ctx.loader,
+ stack_layouter: StackLayouter::new(StackContext { space: ctx.space }),
+ flex_layouter: FlexLayouter::new(FlexContext {
+ space: LayoutSpace {
+ dimensions: ctx.space.usable(),
+ padding: SizeBox::zero(),
+ alignment: ctx.space.alignment,
+ shrink_to_fit: true,
+ },
+ flex_spacing: (ctx.style.line_spacing - 1.0) * Size::pt(ctx.style.font_size),
+ }),
style: Cow::Borrowed(ctx.style)
}
}
/// Layout the tree into a box.
- fn layout(mut self) -> LayoutResult<BoxLayout> {
+ fn layout(mut self) -> LayoutResult<MultiLayout> {
// Walk all nodes and layout them.
for node in &self.tree.nodes {
match node {
@@ -109,7 +164,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
// Add a space.
Node::Space => {
- if !self.flex_layout.is_empty() {
+ if !self.flex_layouter.is_empty() {
self.layout_text(" ", true)?;
}
},
@@ -122,7 +177,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
// Add some paragraph spacing.
let size = Size::pt(self.style.font_size)
* (self.style.line_spacing * self.style.paragraph_spacing - 1.0);
- self.box_layouter.add_space(size)?;
+ self.stack_layouter.add_space(size)?;
},
// Toggle the text styles.
@@ -136,24 +191,26 @@ impl<'a, 'p> Layouter<'a, 'p> {
}
// If there are remainings, add them to the layout.
- if !self.flex_layout.is_empty() {
+ if !self.flex_layouter.is_empty() {
self.layout_flex()?;
}
- Ok(self.box_layouter.finish())
+ Ok(MultiLayout {
+ layouts: vec![self.stack_layouter.finish()]
+ })
}
/// Layout a piece of text into a box.
fn layout_text(&mut self, text: &str, glue: bool) -> LayoutResult<()> {
- let boxed = self::text::layout(text, TextContext {
- loader: &self.loader,
+ let boxed = layout_text(text, TextContext {
+ loader: &self.ctx.loader,
style: &self.style,
})?;
if glue {
- self.flex_layout.add_glue(boxed);
+ self.flex_layouter.add_glue(boxed);
} else {
- self.flex_layout.add_box(boxed);
+ self.flex_layouter.add(boxed);
}
Ok(())
@@ -161,48 +218,46 @@ impl<'a, 'p> Layouter<'a, 'p> {
/// Finish the current flex run and return the resulting box.
fn layout_flex(&mut self) -> LayoutResult<()> {
- if self.flex_layout.is_empty() {
+ if self.flex_layouter.is_empty() {
return Ok(());
}
- let mut layout = FlexLayout::new();
- mem::swap(&mut layout, &mut self.flex_layout);
-
- let boxed = layout.finish(FlexContext {
+ let mut layout = FlexLayouter::new(FlexContext {
space: LayoutSpace {
- dimensions: self.box_layouter.remaining(),
+ dimensions: self.stack_layouter.ctx().space.usable(),
padding: SizeBox::zero(),
- alignment: self.box_layouter.ctx.space.alignment,
+ alignment: self.ctx.space.alignment,
shrink_to_fit: true,
},
flex_spacing: (self.style.line_spacing - 1.0) * Size::pt(self.style.font_size),
- })?;
+ });
+ mem::swap(&mut layout, &mut self.flex_layouter);
+
+ let boxed = layout.finish()?;
- self.box_layouter.add_box(boxed)
+ self.stack_layouter.add_box(boxed)
}
/// Layout a function.
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
- let layout = func.body.layout(LayoutContext {
- loader: &self.loader,
+ let commands = func.body.layout(LayoutContext {
+ loader: &self.ctx.loader,
style: &self.style,
space: LayoutSpace {
- dimensions: self.box_layouter.remaining(),
+ dimensions: self.stack_layouter.remaining(),
padding: SizeBox::zero(),
- alignment: self.box_layouter.ctx.space.alignment,
+ alignment: self.ctx.space.alignment,
shrink_to_fit: true,
},
+ extra_space: self.ctx.extra_space,
})?;
- // Add the potential layout.
- if let Some(layout) = layout {
- match layout {
- Layout::Boxed(boxed) => {
- // Finish the previous flex run before adding the box.
- self.layout_flex()?;
- self.box_layouter.add_box(boxed)?;
- },
- Layout::Flex(flex) => self.flex_layout.add_flexible(flex),
+ for command in commands {
+ match command {
+ Command::Layout(tree) => unimplemented!(),
+ Command::Add(layout) => unimplemented!(),
+ Command::AddMany(layouts) => unimplemented!(),
+ Command::ToggleStyleClass(class) => self.style.to_mut().toggle_class(class),
}
}
diff --git a/src/layout/boxed.rs b/src/layout/stacked.rs
index 7ea9e4fb..78eb0058 100644
--- a/src/layout/boxed.rs
+++ b/src/layout/stacked.rs
@@ -1,68 +1,27 @@
-//! Block-style layouting of boxes.
-
-use std::io::{self, Write};
-use crate::doc::{Document, Page};
-use crate::size::{Size, Size2D};
use super::*;
-/// A box layout has a fixed width and height and composes of actions.
-#[derive(Debug, Clone)]
-pub struct BoxLayout {
- /// The size of the box.
- pub dimensions: Size2D,
- /// The actions composing this layout.
- pub actions: Vec<LayoutAction>,
- /// Whether to debug-render this box.
- pub debug_render: bool,
-}
-
-impl BoxLayout {
- /// Convert this layout into a document.
- pub fn into_doc(self) -> Document {
- Document {
- pages: vec![Page {
- width: self.dimensions.x,
- height: self.dimensions.y,
- actions: self.actions,
- }],
- }
- }
-
- /// Serialize this layout into a string representation.
- pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
- writeln!(f, "{:.4} {:.4}", self.dimensions.x.to_pt(), self.dimensions.y.to_pt())?;
- for action in &self.actions {
- action.serialize(f)?;
- writeln!(f)?;
- }
- Ok(())
- }
-}
-
-/// The context for layouting boxes.
-#[derive(Debug, Copy, Clone)]
-pub struct BoxContext {
- /// The space to layout the boxes in.
- pub space: LayoutSpace,
-}
-
/// Layouts boxes block-style.
#[derive(Debug)]
-pub struct BoxLayouter {
- pub ctx: BoxContext,
+pub struct StackLayouter {
+ ctx: StackContext,
actions: LayoutActionList,
dimensions: Size2D,
usable: Size2D,
cursor: Size2D,
}
-impl BoxLayouter {
+#[derive(Debug, Copy, Clone)]
+pub struct StackContext {
+ pub space: LayoutSpace,
+}
+
+impl StackLayouter {
/// Create a new box layouter.
- pub fn new(ctx: BoxContext) -> BoxLayouter {
+ pub fn new(ctx: StackContext) -> StackLayouter {
let space = ctx.space;
- BoxLayouter {
+ StackLayouter {
ctx,
actions: LayoutActionList::new(),
dimensions: match ctx.space.alignment {
@@ -77,52 +36,54 @@ impl BoxLayouter {
}
}
+ /// Get a reference to this layouter's context.
+ pub fn ctx(&self) -> &StackContext {
+ &self.ctx
+ }
+
/// Add a sublayout.
- pub fn add_box(&mut self, layout: BoxLayout) -> LayoutResult<()> {
+ pub fn add_box(&mut self, layout: Layout) -> LayoutResult<()> {
// In the flow direction (vertical) add the layout and in the second
// direction just consider the maximal size of any child layout.
- let new = Size2D {
+ let new_size = Size2D {
x: crate::size::max(self.dimensions.x, layout.dimensions.x),
y: self.dimensions.y + layout.dimensions.y,
};
// Check whether this box fits.
- if self.overflows(new) {
+ if self.overflows(new_size) {
return Err(LayoutError::NotEnoughSpace);
}
- // Apply the dimensions if they fit.
- self.dimensions = new;
- let width = layout.dimensions.x;
- let height = layout.dimensions.y;
+ self.dimensions = new_size;
+ // Determine where to put the box. When we right-align it, we want the
+ // cursor to point to the top-right corner of the box. Therefore, the
+ // position has to be moved to the left by the width of the box.
let position = match self.ctx.space.alignment {
Alignment::Left => self.cursor,
- Alignment::Right => self.cursor - Size2D::with_x(width),
+ Alignment::Right => self.cursor - Size2D::with_x(layout.dimensions.x),
};
- // Add the box.
- self.add_box_absolute(position, layout);
+ self.cursor.y += layout.dimensions.y;
- // Adjust the cursor.
- self.cursor.y += height;
+ self.add_box_absolute(position, layout);
Ok(())
}
/// Add a sublayout at an absolute position.
- pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) {
- self.actions.add_box(position, layout);
+ pub fn add_box_absolute(&mut self, position: Size2D, layout: Layout) -> LayoutResult<()> {
+ Ok(self.actions.add_box(position, layout))
}
- /// Add some space in between two boxes.
+ /// Add space in between two boxes.
pub fn add_space(&mut self, space: Size) -> LayoutResult<()> {
// Check whether this space fits.
if self.overflows(self.dimensions + Size2D::with_y(space)) {
return Err(LayoutError::NotEnoughSpace);
}
- // Adjust the sizes.
self.cursor.y += space;
self.dimensions.y += space;
@@ -143,8 +104,8 @@ impl BoxLayouter {
}
/// Finish the layouting and create a box layout from this.
- pub fn finish(self) -> BoxLayout {
- BoxLayout {
+ pub fn finish(self) -> Layout {
+ Layout {
dimensions: if self.ctx.space.shrink_to_fit {
self.dimensions.padded(self.ctx.space.padding)
} else {
diff --git a/src/layout/text.rs b/src/layout/text.rs
index ecec0220..6cbe3b8e 100644
--- a/src/layout/text.rs
+++ b/src/layout/text.rs
@@ -17,7 +17,7 @@ pub struct TextContext<'a, 'p> {
}
/// Layout one piece of text without any breaks as one continous box.
-pub fn layout(text: &str, ctx: TextContext) -> LayoutResult<BoxLayout> {
+pub fn layout_text(text: &str, ctx: TextContext) -> LayoutResult<Layout> {
let mut loader = ctx.loader.borrow_mut();
let mut actions = Vec::new();
@@ -88,7 +88,7 @@ pub fn layout(text: &str, ctx: TextContext) -> LayoutResult<BoxLayout> {
actions.push(LayoutAction::WriteText(buffer));
}
- Ok(BoxLayout {
+ Ok(Layout {
dimensions: Size2D::new(width, Size::pt(ctx.style.font_size)),
actions,
debug_render: false,
diff --git a/src/lib.rs b/src/lib.rs
index 2fe2f94f..b622d924 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -17,17 +17,15 @@ pub extern crate toddle;
use std::cell::RefCell;
use toddle::query::{FontLoader, SharedFontLoader, FontProvider};
-use crate::doc::Document;
use crate::func::Scope;
use crate::parsing::{parse, ParseContext, ParseResult, ParseError};
-use crate::layout::{layout, LayoutContext, Alignment, LayoutSpace, LayoutError, LayoutResult};
-use crate::layout::boxed::BoxLayout;
+use crate::layout::{layout_tree, LayoutContext, MultiLayout};
+use crate::layout::{LayoutSpace, Alignment, LayoutError, LayoutResult};
use crate::style::{PageStyle, TextStyle};
use crate::syntax::SyntaxTree;
#[macro_use]
mod macros;
-pub mod doc;
pub mod export;
pub mod func;
pub mod layout;
@@ -79,6 +77,12 @@ impl<'p> Typesetter<'p> {
self.loader.get_mut().add_provider(provider);
}
+ /// A reference to the backing font loader.
+ #[inline]
+ pub fn loader(&self) -> &SharedFontLoader<'p> {
+ &self.loader
+ }
+
/// Parse source code into a syntax tree.
pub fn parse(&self, src: &str) -> ParseResult<SyntaxTree> {
let scope = Scope::with_std();
@@ -86,31 +90,29 @@ impl<'p> Typesetter<'p> {
}
/// Layout a syntax tree and return the layout and the referenced font list.
- pub fn layout(&self, tree: &SyntaxTree) -> LayoutResult<BoxLayout> {
- let pages = layout(&tree, LayoutContext {
+ pub fn layout(&self, tree: &SyntaxTree) -> LayoutResult<MultiLayout> {
+ let space = LayoutSpace {
+ dimensions: self.page_style.dimensions,
+ padding: self.page_style.margins,
+ alignment: Alignment::Left,
+ shrink_to_fit: false,
+ };
+
+ let pages = layout_tree(&tree, LayoutContext {
loader: &self.loader,
style: &self.text_style,
- space: LayoutSpace {
- dimensions: self.page_style.dimensions,
- padding: self.page_style.margins,
- alignment: Alignment::Left,
- shrink_to_fit: false,
- },
+ space,
+ extra_space: Some(space),
})?;
+
Ok(pages)
}
/// Typeset a portable document from source code.
- pub fn typeset(&self, src: &str) -> Result<Document, TypesetError> {
+ pub fn typeset(&self, src: &str) -> Result<MultiLayout, TypesetError> {
let tree = self.parse(src)?;
let layout = self.layout(&tree)?;
- let document = layout.into_doc();
- Ok(document)
- }
-
- /// A reference to the backing font loader.
- pub fn loader(&self) -> &SharedFontLoader<'p> {
- &self.loader
+ Ok(layout)
}
}
diff --git a/src/library/align.rs b/src/library/align.rs
index 4b2ee8c3..b1e41abb 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -38,12 +38,13 @@ impl Function for AlignFunc {
Ok(AlignFunc { alignment, body })
}
- fn layout(&self, mut ctx: LayoutContext) -> LayoutResult<Option<Layout>> {
+ fn layout(&self, ctx: LayoutContext) -> LayoutResult<FuncCommands> {
if let Some(body) = &self.body {
- // Override the previous alignment and do the layouting.
- ctx.space.alignment = self.alignment;
- layout(body, ctx)
- .map(|l| Some(Layout::Boxed(l)))
+ // // Override the previous alignment and do the layouting.
+ // ctx.space.alignment = self.alignment;
+ // layout(body, ctx)
+ // .map(|l| Some(Layout::Boxed(l)))
+ Ok(FuncCommands::new())
} else {
unimplemented!("context-modifying align func")
}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 9323c4c0..acbd11db 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -9,10 +9,9 @@ mod styles;
pub mod prelude {
pub use crate::syntax::{SyntaxTree, FuncHeader, Expression};
pub use crate::parsing::{parse, ParseContext, ParseResult, ParseError};
- pub use crate::layout::{layout, Layout, LayoutContext, LayoutResult, LayoutError};
- pub use crate::layout::flex::FlexLayout;
- pub use crate::layout::boxed::BoxLayout;
- pub use crate::func::Function;
+ pub use crate::layout::{layout_tree, layout_text, MultiLayout, Layout, LayoutContext};
+ pub use crate::layout::{LayoutResult, LayoutError};
+ pub use crate::func::{Function, Command, FuncCommands};
pub fn err<S: Into<String>, T>(message: S) -> ParseResult<T> {
Err(ParseError::new(message))
diff --git a/src/library/styles.rs b/src/library/styles.rs
index c84b9fed..28cc0e48 100644
--- a/src/library/styles.rs
+++ b/src/library/styles.rs
@@ -1,7 +1,7 @@
//! Basic style functions: bold, italic, monospace.
use super::prelude::*;
-use toddle::query::FontClass;
+// use toddle::query::FontClass;
@@ -27,19 +27,20 @@ macro_rules! style_func {
}
}
- fn layout(&self, ctx: LayoutContext) -> LayoutResult<Option<Layout>> {
- // Change the context.
- let mut $style = ctx.style.clone();
- $style_change
+ fn layout(&self, ctx: LayoutContext) -> LayoutResult<FuncCommands> {
+ // // Change the context.
+ // let mut $style = ctx.style.clone();
+ // $style_change
- // Create a box and put it into a flex layout.
- let boxed = layout(&self.body, LayoutContext {
- style: &$style,
- .. ctx
- })?;
- let flex = FlexLayout::from_box(boxed);
+ // // Create a box and put it into a flex layout.
+ // let boxed = layout(&self.body, LayoutContext {
+ // style: &$style,
+ // .. ctx
+ // })?;
+ // let flex = FlexLayout::from_box(boxed);
- Ok(Some(Layout::Flex(flex)))
+ // Ok(Some(Layout::Flex(flex)))
+ Ok(FuncCommands::new())
}
}
};
diff --git a/src/parsing/mod.rs b/src/parsing/mod.rs
index 344f3577..7d2c3d21 100644
--- a/src/parsing/mod.rs
+++ b/src/parsing/mod.rs
@@ -432,8 +432,8 @@ error_type! {
#[cfg(test)]
mod tests {
use super::*;
- use crate::func::{Function, Scope};
- use crate::layout::{LayoutContext, LayoutResult, Layout};
+ use crate::func::{Function, FuncCommands, Scope};
+ use crate::layout::{LayoutContext, LayoutResult};
use Node::{Space as S, Newline as N, Func as F};
use funcs::*;
@@ -456,7 +456,9 @@ mod tests {
}
}
- fn layout(&self, _: LayoutContext) -> LayoutResult<Option<Layout>> { Ok(None) }
+ fn layout(&self, _: LayoutContext) -> LayoutResult<FuncCommands> {
+ Ok(FuncCommands::new())
+ }
}
/// A testing function without a body.
@@ -473,7 +475,9 @@ mod tests {
}
}
- fn layout(&self, _: LayoutContext) -> LayoutResult<Option<Layout>> { Ok(None) }
+ fn layout(&self, _: LayoutContext) -> LayoutResult<FuncCommands> {
+ Ok(FuncCommands::new())
+ }
}
}