Refactor layout types 🚧

This commit is contained in:
Laurenz 2019-10-13 12:08:07 +02:00
parent 6f22e4f13c
commit 463e4ebd82
14 changed files with 352 additions and 323 deletions

View File

@ -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>,
}

View File

@ -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);
}

View File

@ -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 +

View File

@ -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));
}

View File

@ -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<FlexUnit>,
}
/// 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<FlexUnit>,
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<BoxLayout> {
FlexFinisher::new(self, ctx).finish()
}
line_content: Vec<(Size2D, Layout)>,
line_metrics: Size2D,
last_glue: Option<Layout>,
}
/// 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<FlexUnit>,
ctx: FlexContext,
actions: LayoutActionList,
dimensions: Size2D,
usable: Size2D,
cursor: Size2D,
line_metrics: Size2D,
line_content: Vec<(Size2D, BoxLayout)>,
glue: Option<BoxLayout>,
/// 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<BoxLayout> {
/// 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<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));

View File

@ -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);
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),
}
}

View File

@ -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 {

View File

@ -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,

View File

@ -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)
}
}

View File

@ -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")
}

View File

@ -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))

View File

@ -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())
}
}
};

View File

@ -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())
}
}
}

View File

@ -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();
}