diff --git a/src/doc.rs b/src/doc.rs deleted file mode 100644 index 9a287913d..000000000 --- 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, -} - -/// 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, -} diff --git a/src/export/pdf.rs b/src/export/pdf.rs index 3a3f2a9c7..78e48e81d 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(&self, document: &Document, loader: &SharedFontLoader, target: W) + pub fn export(&self, layout: &MultiLayout, loader: &SharedFontLoader, target: W) -> PdfResult { - 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, - doc: &'d Document, + layout: &'d MultiLayout, offsets: Offsets, font_remap: HashMap, fonts: Vec, @@ -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> { - // Create a subsetted PDF font for each font in the document. + fn new(layout: &'d MultiLayout, loader: &SharedFontLoader, target: W) + -> PdfResult> { + // 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 { 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 776f0e98d..a7044b6e9 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>; + fn layout(&self, ctx: LayoutContext) -> LayoutResult; } 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 +} + +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; + + 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 f76b61c3a..3eb85e50c 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 75674e8f9..68d390040 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -4,62 +4,21 @@ 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, -} -/// A unit in a flex layout. -#[derive(Debug, Clone)] -pub enum FlexUnit { - /// A content unit to be arranged flexibly. - Boxed(BoxLayout), - /// 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), -} +/// Finishes a flex layout by justifying the positions of the individual boxes. +#[derive(Debug)] +pub struct FlexLayouter { + ctx: FlexContext, + units: Vec, -impl FlexLayout { - /// Create a new flex layout. - pub fn new() -> FlexLayout { - FlexLayout { - units: vec![], - } - } + actions: LayoutActionList, + dimensions: Size2D, + usable: Size2D, + cursor: Size2D, - /// Create a new flex layout containing just one box. - pub fn from_box(boxed: BoxLayout) -> FlexLayout { - FlexLayout { - units: vec![FlexUnit::Boxed(boxed)], - } - } - - /// Add a sublayout. - pub fn add_box(&mut self, layout: BoxLayout) { - 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) { - 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 { - FlexFinisher::new(self, ctx).finish() - } + line_content: Vec<(Size2D, Layout)>, + line_metrics: Size2D, + last_glue: Option, } /// The context for flex layouting. @@ -71,42 +30,59 @@ pub struct FlexContext { pub flex_spacing: Size, } -/// Finishes a flex layout by justifying the positions of the individual boxes. -#[derive(Debug)] -struct FlexFinisher { - units: Vec, - ctx: FlexContext, - actions: LayoutActionList, - dimensions: Size2D, - usable: Size2D, - cursor: Size2D, - line_metrics: Size2D, - line_content: Vec<(Size2D, BoxLayout)>, - glue: Option, +/// A unit in a flex layout. +#[derive(Debug, Clone)] +enum FlexUnit { + /// A content unit to be arranged flexibly. + 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(Layout), } -impl FlexFinisher { - /// Create the finisher from the layout. - fn new(layout: FlexLayout, ctx: FlexContext) -> FlexFinisher { - let space = ctx.space; - FlexFinisher { - units: layout.units, +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(space.usable().x), + Alignment::Right => Size2D::with_x(ctx.space.usable().x), }, - usable: space.usable(), - cursor: Size2D::new(space.padding.left, space.padding.top), - line_metrics: Size2D::zero(), + usable: ctx.space.usable(), + cursor: Size2D::new(ctx.space.padding.left, ctx.space.padding.top), + line_content: vec![], - glue: None, + line_metrics: Size2D::zero(), + last_glue: None, } } - /// Finish the flex layout into the justified box layout. - fn finish(mut self) -> LayoutResult { + /// Get a reference to this layouter's context. + pub fn ctx(&self) -> &FlexContext { + &self.ctx + } + + /// Add a sublayout. + 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: Layout) { + self.units.push(FlexUnit::Glue(glue)); + } + + /// Whether this layouter contains any items. + pub fn is_empty(&self) -> bool { + self.units.is_empty() + } + + /// Compute the justified layout. + pub fn finish(mut self) -> LayoutResult { // 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 5cc8d8f25..195433896 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, + /// 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 { - Layouter::new(tree, ctx).layout() +impl Layout { + /// Serialize this layout into an output buffer. + pub fn serialize(&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, +} + +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, } /// 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 { + 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 { + fn layout(mut self) -> LayoutResult { // 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); - self.box_layouter.add_box(boxed) + let boxed = layout.finish()?; + + 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 similarity index 58% rename from src/layout/boxed.rs rename to src/layout/stacked.rs index 7ea9e4fb3..78eb0058b 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, - /// 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(&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 ecec02208..6cbe3b8e6 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 { +pub fn layout_text(text: &str, ctx: TextContext) -> LayoutResult { let mut loader = ctx.loader.borrow_mut(); let mut actions = Vec::new(); @@ -88,7 +88,7 @@ pub fn layout(text: &str, ctx: TextContext) -> LayoutResult { 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 2fe2f94fe..b622d924d 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 { 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 { - let pages = layout(&tree, LayoutContext { + pub fn layout(&self, tree: &SyntaxTree) -> LayoutResult { + 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 { + pub fn typeset(&self, src: &str) -> Result { 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 4b2ee8c3e..b1e41abb8 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> { + fn layout(&self, ctx: LayoutContext) -> LayoutResult { 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 9323c4c0e..acbd11db4 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, T>(message: S) -> ParseResult { Err(ParseError::new(message)) diff --git a/src/library/styles.rs b/src/library/styles.rs index c84b9fed6..28cc0e480 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> { - // Change the context. - let mut $style = ctx.style.clone(); - $style_change + fn layout(&self, ctx: LayoutContext) -> LayoutResult { + // // 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 344f3577d..7d2c3d216 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> { Ok(None) } + fn layout(&self, _: LayoutContext) -> LayoutResult { + Ok(FuncCommands::new()) + } } /// A testing function without a body. @@ -473,7 +475,9 @@ mod tests { } } - fn layout(&self, _: LayoutContext) -> LayoutResult> { Ok(None) } + fn layout(&self, _: LayoutContext) -> LayoutResult { + Ok(FuncCommands::new()) + } } } diff --git a/tests/layouting.rs b/tests/layouting.rs index 2ac92b8e4..0174b097c 100644 --- a/tests/layouting.rs +++ b/tests/layouting.rs @@ -11,17 +11,19 @@ const CACHE_DIR: &str = "test-cache"; fn main() { + let mut perfect_match = false; let mut filter = Vec::new(); + for arg in std::env::args().skip(1) { - if arg.as_str() != "--nocapture" { + if arg.as_str() == "--nocapture" { + continue; + } else if arg.as_str() == "=" { + perfect_match = true; + } else { filter.push(arg); } } - if !filter.is_empty() { - println!("Using filter: {:?}", filter); - } - fs::create_dir_all(format!("{}/serialized", CACHE_DIR)).unwrap(); fs::create_dir_all(format!("{}/rendered", CACHE_DIR)).unwrap(); fs::create_dir_all(format!("{}/pdf", CACHE_DIR)).unwrap(); @@ -33,7 +35,13 @@ fn main() { .file_stem().unwrap() .to_str().unwrap(); - if filter.is_empty() || filter.iter().any(|pattern| name.contains(pattern)) { + let matches = if perfect_match { + filter.iter().any(|pattern| name == pattern) + } else { + filter.is_empty() || filter.iter().any(|pattern| name.contains(pattern)) + }; + + if matches { let mut file = File::open(&path).unwrap(); let mut src = String::new(); file.read_to_string(&mut src).unwrap(); @@ -62,7 +70,8 @@ fn test(name: &str, src: &str) { // Find all used fonts and their filenames. let mut map = Vec::new(); let mut loader = typesetter.loader().borrow_mut(); - for action in &layout.actions { + let single = &layout.layouts[0]; + for action in &single.actions { if let LayoutAction::SetFont(index, _) = action { if map.iter().find(|(i, _)| i == index).is_none() { let (_, provider_index) = loader.get_provider_and_index(*index); @@ -78,7 +87,7 @@ fn test(name: &str, src: &str) { for (index, path) in map { writeln!(file, "{} {}", index, path).unwrap(); } - layout.serialize(&mut file).unwrap(); + single.serialize(&mut file).unwrap(); // Render the layout into a PNG. Command::new("python") @@ -90,7 +99,6 @@ fn test(name: &str, src: &str) { // Write the PDF file. let path = format!("{}/pdf/{}.pdf", CACHE_DIR, name); let file = BufWriter::new(File::create(path).unwrap()); - let document = layout.into_doc(); let exporter = PdfExporter::new(); - exporter.export(&document, typesetter.loader(), file).unwrap(); + exporter.export(&layout, typesetter.loader(), file).unwrap(); }