diff --git a/src/doc.rs b/src/doc.rs index 304b4936e..f08332f6b 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -1,7 +1,7 @@ //! Representation of typesetted documents. use crate::font::Font; -use crate::layout::{Size, Position}; +use crate::size::{Size, Size2D}; /// A complete typesetted document, which can be exported. @@ -28,9 +28,9 @@ pub struct Page { #[derive(Debug, Clone)] pub enum TextAction { /// Move to an absolute position. - MoveAbsolute(Position), + MoveAbsolute(Size2D), /// Move from the _start_ of the current line by an (x, y) offset. - MoveNewline(Position), + MoveNewline(Size2D), /// Write text starting at the current position. WriteText(String), /// Set the font by index and font size. diff --git a/src/export/pdf.rs b/src/export/pdf.rs index c3f67afa6..1c0d52b05 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -10,7 +10,7 @@ use pdf::font::{GlyphUnit, CMap, CMapEncoding, WidthRecord, FontStream}; use crate::doc::{Document, Page as DocPage, TextAction}; use crate::font::{Font, FontError}; -use crate::layout::Size; +use crate::size::Size; /// Exports documents into _PDFs_. diff --git a/src/font.rs b/src/font.rs index 6b074e735..fada64efc 100644 --- a/src/font.rs +++ b/src/font.rs @@ -22,7 +22,7 @@ use opentype::{Error as OpentypeError, OpenTypeReader, Outlines, TableRecord, Ta use opentype::tables::{Header, Name, CharMap, MaximumProfile, HorizontalMetrics, Post, OS2}; use opentype::global::{MacStyleFlags, NameEntry}; -use crate::layout::Size; +use crate::size::Size; /// A loaded and parsed font program. diff --git a/src/layout/boxed.rs b/src/layout/boxed.rs index c240db0be..b75ea75ad 100644 --- a/src/layout/boxed.rs +++ b/src/layout/boxed.rs @@ -1,10 +1,34 @@ -//! Layouting of layout boxes. +//! Definitive layouting of boxes. -use crate::doc::TextAction; -use super::{Layouter, Layout, LayoutContext, LayoutResult, Position}; +use crate::doc::{Document, Page, TextAction}; +use crate::font::Font; +use super::{Layouter, LayoutContext, Size2D}; -/// Layouts sublayouts within the constraints of a layouting context. +/// A box layout has a fixed width and height and consists of actions. +#[derive(Debug, Clone)] +pub struct BoxLayout { + /// The size of the box. + dimensions: Size2D, + /// The actions composing this layout. + actions: Vec, +} + +impl BoxLayout { + /// Convert this layout into a document given the list of fonts referenced by it. + pub fn into_doc(self, fonts: Vec) -> Document { + Document { + pages: vec![Page { + width: self.dimensions.x, + height: self.dimensions.y, + actions: self.actions, + }], + fonts, + } + } +} + +/// Layouts boxes block-style. #[derive(Debug)] pub struct BoxLayouter<'a, 'p> { ctx: &'a LayoutContext<'a, 'p>, @@ -21,17 +45,29 @@ impl<'a, 'p> BoxLayouter<'a, 'p> { } /// Add a sublayout. - pub fn add_layout_absolute(&mut self, position: Position, layout: Layout) { + pub fn add_box(&mut self, layout: BoxLayout) { + unimplemented!() + } + + /// Add a sublayout at an absolute position. + pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) { self.actions.push(TextAction::MoveAbsolute(position)); self.actions.extend(layout.actions); } } impl Layouter for BoxLayouter<'_, '_> { - fn finish(self) -> LayoutResult { - Ok(Layout { - extent: self.ctx.max_extent.clone(), + type Layout = BoxLayout; + + /// Finish the layouting and create a box layout from this. + fn finish(self) -> BoxLayout { + BoxLayout { + dimensions: self.ctx.space.dimensions.clone(), actions: self.actions - }) + } + } + + fn is_empty(&self) -> bool { + self.actions.is_empty() } } diff --git a/src/layout/flex.rs b/src/layout/flex.rs new file mode 100644 index 000000000..faddc95a6 --- /dev/null +++ b/src/layout/flex.rs @@ -0,0 +1,61 @@ +//! Flexible and lazy layouting of boxes. + +use super::{Layouter, LayoutContext, BoxLayout}; + + +/// A flex layout consists of a yet unarranged list of boxes. +#[derive(Debug, Clone)] +pub struct FlexLayout { + /// The sublayouts composing this layout. + layouts: Vec, +} + +impl FlexLayout { + /// Compute the layout. + pub fn into_box(self) -> BoxLayout { + // TODO: Do the justification. + unimplemented!() + } +} + +/// Layouts boxes next to each other (inline-style) lazily. +#[derive(Debug)] +pub struct FlexLayouter<'a, 'p> { + ctx: &'a LayoutContext<'a, 'p>, + layouts: Vec, +} + +impl<'a, 'p> FlexLayouter<'a, 'p> { + /// Create a new flex layouter. + pub fn new(ctx: &'a LayoutContext<'a, 'p>) -> FlexLayouter<'a, 'p> { + FlexLayouter { + ctx, + layouts: vec![], + } + } + + /// Add a sublayout. + pub fn add_box(&mut self, layout: BoxLayout) { + self.layouts.push(layout); + } + + /// Add all sublayouts of another flex layout. + pub fn add_flexible(&mut self, layout: FlexLayout) { + self.layouts.extend(layout.layouts); + } +} + +impl Layouter for FlexLayouter<'_, '_> { + type Layout = FlexLayout; + + /// Finish the layouting and create a flexible layout from this. + fn finish(self) -> FlexLayout { + FlexLayout { + layouts: self.layouts + } + } + + fn is_empty(&self) -> bool { + self.layouts.is_empty() + } +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 1c1f743e5..8b0c30041 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,74 +1,35 @@ //! The layouting engine. -use crate::doc::{Document, Page, TextAction}; -use crate::font::{Font, FontLoader, FontFamily, FontError}; +use crate::font::{FontLoader, FontError}; +use crate::size::{Size2D, SizeBox}; use crate::syntax::{SyntaxTree, Node}; +use crate::style::TextStyle; -mod size; -mod text; mod boxed; +mod flex; -pub use size::{Size, Position, Extent}; -pub use text::TextLayouter; -pub use boxed::BoxLayouter; +pub use flex::{FlexLayout, FlexLayouter}; +pub use boxed::{BoxLayout, BoxLayouter}; -/// Layout a syntax tree in a given context. -pub fn layout(tree: &SyntaxTree, ctx: &LayoutContext) -> LayoutResult { - let mut layouter = TextLayouter::new(ctx); +/// Types that layout components and can be finished into some kind of layout. +pub trait Layouter { + type Layout; - let mut italic = false; - let mut bold = false; + /// Finish the layouting and create the layout from this. + fn finish(self) -> Self::Layout; - for node in &tree.nodes { - match node { - Node::Text(text) => layouter.add_text(text)?, - Node::Space => layouter.add_space()?, - Node::Newline => layouter.add_paragraph()?, - - Node::ToggleItalics => { - italic = !italic; - layouter.set_italic(italic); - }, - Node::ToggleBold => { - bold = !bold; - layouter.set_bold(bold); - } - - Node::Func(_) => unimplemented!(), - } - } - - layouter.finish() + /// Whether this layouter contains any items. + fn is_empty(&self) -> bool; } /// A collection of layouted content. #[derive(Debug, Clone)] -pub struct Layout { - /// The extent of this layout into all directions. - extent: Extent, - /// Actions composing this layout. - actions: Vec, -} - -impl Layout { - /// Convert this layout into a document given the list of fonts referenced by it. - pub fn into_document(self, fonts: Vec) -> Document { - Document { - pages: vec![Page { - width: self.extent.width, - height: self.extent.height, - actions: self.actions, - }], - fonts, - } - } -} - -/// Types supporting some kind of layouting. -pub trait Layouter { - /// Finishing the current layouting process and return a layout. - fn finish(self) -> LayoutResult; +pub enum Layout { + /// A box layout. + Boxed(BoxLayout), + /// A flexible layout. + Flex(FlexLayout), } /// The context for layouting. @@ -76,70 +37,61 @@ pub trait Layouter { pub struct LayoutContext<'a, 'p> { /// Loads fonts matching queries. pub loader: &'a FontLoader<'p>, - /// The spacial constraints to layout in. - pub max_extent: Extent, /// Base style to set text with. - pub text_style: TextStyle, + pub style: TextStyle, + /// The space to layout in. + pub space: LayoutSpace, } -/// Default styles for text. +/// Spacial constraints for layouting. #[derive(Debug, Clone)] -pub struct TextStyle { - /// A fallback list of font families to use. - pub font_families: Vec, - /// The font size. - pub font_size: f32, - /// The line spacing (as a multiple of the font size). - pub line_spacing: f32, - /// The paragraphs spacing (as a multiple of the line spacing). - pub paragraph_spacing: f32, +pub struct LayoutSpace { + /// The maximum size of the box to layout in. + pub dimensions: Size2D, + /// Padding that should be respected on each side. + pub padding: SizeBox, } -impl Default for TextStyle { - fn default() -> TextStyle { - use FontFamily::*; - TextStyle { - // Default font family, font size and line spacing. - font_families: vec![SansSerif, Serif, Monospace], - font_size: 11.0, - line_spacing: 1.25, - paragraph_spacing: 1.5, +/// Layout a syntax tree in a given context. +pub fn layout(tree: &SyntaxTree, ctx: &LayoutContext) -> LayoutResult { + // The top-level layouter and the sub-level layouter. + let mut box_layouter = BoxLayouter::new(ctx); + let mut flex_layouter = FlexLayouter::new(ctx); + + // The current text style. + let mut italic = false; + let mut bold = false; + + // Walk all nodes and layout them. + for node in &tree.nodes { + match node { + Node::Text(text) => { + unimplemented!() + }, + Node::Space => { + unimplemented!() + }, + Node::Newline => { + unimplemented!() + }, + + // Toggle the text styles. + Node::ToggleItalics => italic = !italic, + Node::ToggleBold => bold = !bold, + + Node::Func(func) => { + unimplemented!() + } } } -} -/// Default styles for pages. -#[derive(Debug, Clone)] -pub struct PageStyle { - /// The width of the page. - pub width: Size, - /// The height of the page. - pub height: Size, - - /// The amount of white space on the left side. - pub margin_left: Size, - /// The amount of white space on the top side. - pub margin_top: Size, - /// The amount of white space on the right side. - pub margin_right: Size, - /// The amount of white space on the bottom side. - pub margin_bottom: Size, -} - -impl Default for PageStyle { - fn default() -> PageStyle { - PageStyle { - // A4 paper. - width: Size::from_mm(210.0), - height: Size::from_mm(297.0), - - // All the same margins. - margin_left: Size::from_cm(3.0), - margin_top: Size::from_cm(3.0), - margin_right: Size::from_cm(3.0), - margin_bottom: Size::from_cm(3.0), - } + // If there are remainings, add them to the layout. + if !flex_layouter.is_empty() { + let boxed = flex_layouter.finish().into_box(); + box_layouter.add_box(boxed); } + + Ok(box_layouter.finish()) } /// The error type for layouting. diff --git a/src/layout/size.rs b/src/layout/size.rs deleted file mode 100644 index 92a4c1131..000000000 --- a/src/layout/size.rs +++ /dev/null @@ -1,173 +0,0 @@ -//! A general spacing type. - -use std::cmp::Ordering; -use std::fmt::{self, Display, Debug, Formatter}; -use std::iter::Sum; -use std::ops::*; - - -/// A position in 2-dimensional space. -#[derive(Debug, Copy, Clone, PartialEq, Default)] -pub struct Position { - /// The horizontal coordinate. - pub x: Size, - /// The vertical coordinate. - pub y: Size, -} - -impl Position { - /// Create a zeroed position. - #[inline] - pub fn zero() -> Position { Position { x: Size::zero(), y: Size::zero() } } -} - -/// Size of a box in 2-dimensional space. -#[derive(Debug, Copy, Clone, PartialEq, Default)] -pub struct Extent { - /// The horizontal extent. - pub width: Size, - /// The vertical extent. - pub height: Size, -} - -/// A general spacing type. -#[derive(Copy, Clone, PartialEq, Default)] -pub struct Size { - /// The size in typographic points (1/72 inches). - points: f32, -} - -impl Size { - /// Create a zeroed size. - #[inline] - pub fn zero() -> Size { Size { points: 0.0 } } - - /// Create a size from an amount of points. - #[inline] - pub fn from_points(points: f32) -> Size { Size { points } } - - /// Create a size from an amount of inches. - #[inline] - pub fn from_inches(inches: f32) -> Size { Size { points: 72.0 * inches } } - - /// Create a size from an amount of millimeters. - #[inline] - pub fn from_mm(mm: f32) -> Size { Size { points: 2.83465 * mm } } - - /// Create a size from an amount of centimeters. - #[inline] - pub fn from_cm(cm: f32) -> Size { Size { points: 28.3465 * cm } } - - /// Convert this size into points. - #[inline] - pub fn to_points(&self) -> f32 { self.points } - - /// Convert this size into inches. - #[inline] - pub fn to_inches(&self) -> f32 { self.points * 0.0138889 } - - /// Convert this size into millimeters. - #[inline] - pub fn to_mm(&self) -> f32 { self.points * 0.352778 } - - /// Convert this size into centimeters. - #[inline] - pub fn to_cm(&self) -> f32 { self.points * 0.0352778 } -} - -impl Display for Size { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}pt", self.points) - } -} - -impl Debug for Size { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Display::fmt(self, f) - } -} - -impl PartialOrd for Size { - #[inline] - fn partial_cmp(&self, other: &Size) -> Option { - self.points.partial_cmp(&other.points) - } -} - -impl Neg for Size { - type Output = Size; - - #[inline] - fn neg(self) -> Size { - Size { points: -self.points } - } -} - -impl Sum for Size { - #[inline] - fn sum(iter: I) -> Size where I: Iterator { - iter.fold(Size::zero(), Add::add) - } -} - -macro_rules! impl_reflexive { - ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident) => { - impl $trait for Size { - type Output = Size; - - #[inline] - fn $func(self, other: Size) -> Size { - Size { points: $trait::$func(self.points, other.points) } - } - } - - impl $assign_trait for Size { - #[inline] - fn $assign_func(&mut self, other: Size) { - $assign_trait::$assign_func(&mut self.points, other.points); - } - } - }; -} - -macro_rules! impl_num_back { - ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => { - impl $trait<$ty> for Size { - type Output = Size; - - #[inline] - fn $func(self, other: $ty) -> Size { - Size { points: $trait::$func(self.points, other as f32) } - } - } - - impl $assign_trait<$ty> for Size { - #[inline] - fn $assign_func(&mut self, other: $ty) { - $assign_trait::$assign_func(&mut self.points, other as f32); - } - } - }; -} - -macro_rules! impl_num_both { - ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => { - impl_num_back!($trait, $func, $assign_trait, $assign_func, $ty); - - impl $trait for $ty { - type Output = Size; - - #[inline] - fn $func(self, other: Size) -> Size { - Size { points: $trait::$func(self as f32, other.points) } - } - } - }; -} - -impl_reflexive!(Add, add, AddAssign, add_assign); -impl_reflexive!(Sub, sub, SubAssign, sub_assign); -impl_num_both!(Mul, mul, MulAssign, mul_assign, f32); -impl_num_both!(Mul, mul, MulAssign, mul_assign, i32); -impl_num_back!(Div, div, DivAssign, div_assign, f32); -impl_num_back!(Div, div, DivAssign, div_assign, i32); diff --git a/src/layout/text.rs b/src/layout/text.rs deleted file mode 100644 index d1e2afd79..000000000 --- a/src/layout/text.rs +++ /dev/null @@ -1,243 +0,0 @@ -//! Layouting of text. - -use std::cell::Ref; -use std::mem; - -use smallvec::SmallVec; - -use crate::doc::TextAction; -use crate::font::{Font, FontQuery}; -use super::{Layouter, Layout, LayoutError, LayoutContext, LayoutResult, Size, Position}; - - -/// Layouts text within the constraints of a layouting context. -#[derive(Debug)] -pub struct TextLayouter<'a, 'p> { - ctx: &'a LayoutContext<'a, 'p>, - units: Vec, - italic: bool, - bold: bool, -} - -/// A units that is arranged by the text layouter. -#[derive(Debug, Clone)] -enum Unit { - /// A paragraph. - Paragraph, - /// A space with its font index and width. - Space(usize, Size), - /// One logical tex unit. - Text(TextUnit), -} - -/// A logical unit of text (a word, syllable or a similar construct). -#[derive(Debug, Clone)] -struct TextUnit { - /// Contains pairs of (characters, font_index, char_width) for each character of the text. - chars_with_widths: SmallVec<[(char, usize, Size); 12]>, - /// The total width of the unit. - width: Size, -} - -impl<'a, 'p> TextLayouter<'a, 'p> { - /// Create a new text layouter. - pub fn new(ctx: &'a LayoutContext<'a, 'p>) -> TextLayouter<'a, 'p> { - TextLayouter { - ctx, - italic: false, - bold: false, - units: vec![], - } - } - - /// Add more text to the layout. - pub fn add_text(&mut self, text: &str) -> LayoutResult<()> { - let mut chars_with_widths = SmallVec::<[(char, usize, Size); 12]>::new(); - - // Find out which font to use for each character in the text and meanwhile calculate the - // width of the text. - let mut text_width = Size::zero(); - for c in text.chars() { - // Find out the width and add it to the total width. - let (index, font) = self.get_font_for(c)?; - let char_width = self.width_of(c, &font); - text_width += char_width; - - chars_with_widths.push((c, index, char_width)); - } - - self.units.push(Unit::Text(TextUnit { - chars_with_widths, - width: text_width, - })); - - Ok(()) - } - - /// Add a single space character. - pub fn add_space(&mut self) -> LayoutResult<()> { - let (index, font) = self.get_font_for(' ')?; - let width = self.width_of(' ', &font); - drop(font); - Ok(self.units.push(Unit::Space(index, width))) - } - - /// Start a new paragraph. - pub fn add_paragraph(&mut self) -> LayoutResult<()> { - Ok(self.units.push(Unit::Paragraph)) - } - - /// Enable or disable italics. - pub fn set_italic(&mut self, italic: bool) { - self.italic = italic; - } - - /// Enable or disable boldface. - pub fn set_bold(&mut self, bold: bool) { - self.bold = bold; - } - - /// Load a font that has the character we need. - fn get_font_for(&self, character: char) -> LayoutResult<(usize, Ref)> { - self.ctx.loader.get(FontQuery { - families: self.ctx.text_style.font_families.clone(), - italic: self.italic, - bold: self.bold, - character, - }).ok_or_else(|| LayoutError::NoSuitableFont(character)) - } - - /// The width of a char in a specific font. - fn width_of(&self, character: char, font: &Font) -> Size { - font.widths[font.map(character) as usize] * self.ctx.text_style.font_size - } -} - -impl Layouter for TextLayouter<'_, '_> { - fn finish(self) -> LayoutResult { - TextFinisher { - actions: vec![], - buffered_text: String::new(), - current_width: Size::zero(), - active_font: std::usize::MAX, - max_width: self.ctx.max_extent.width, - layouter: self, - }.finish() - } -} - -/// Finishes a text layout by converting the text units into a stream of text actions. -#[derive(Debug)] -struct TextFinisher<'a, 'p> { - layouter: TextLayouter<'a, 'p>, - actions: Vec, - buffered_text: String, - current_width: Size, - active_font: usize, - max_width: Size, -} - -impl<'a, 'p> TextFinisher<'a, 'p> { - /// Finish the layout. - fn finish(mut self) -> LayoutResult { - // Move the units out of the layouter leaving an empty vector in place. This is needed to - // move the units out into the for loop while keeping the borrow checker happy. - let mut units = Vec::new(); - mem::swap(&mut self.layouter.units, &mut units); - - // Move from the origin one line below because the y-component of the origin is the - // baseline. - self.move_newline(1.0); - - for unit in units { - match unit { - Unit::Paragraph => self.write_paragraph(), - Unit::Space(index, width) => self.write_space(index, width), - Unit::Text(text) => self.write_text_unit(text), - } - } - - self.write_buffered_text(); - - Ok(Layout { - extent: self.layouter.ctx.max_extent.clone(), - actions: self.actions, - }) - } - - /// Add a paragraph to the output. - fn write_paragraph(&mut self) { - self.write_buffered_text(); - self.move_newline(self.layouter.ctx.text_style.paragraph_spacing); - } - - /// Add a single space to the output if it is not eaten by a line break. - fn write_space(&mut self, font: usize, width: Size) { - if self.would_overflow(width) { - self.write_buffered_text(); - self.move_newline(1.0); - } else if self.current_width > Size::zero() { - if font != self.active_font { - self.write_buffered_text(); - self.set_font(font); - } - - self.buffered_text.push(' '); - self.current_width += width; - } - } - - /// Add a single unit of text without breaking it apart. - fn write_text_unit(&mut self, text: TextUnit) { - if self.would_overflow(text.width) { - self.write_buffered_text(); - self.move_newline(1.0); - } - - // Finally write the word. - for (c, font, width) in text.chars_with_widths { - if font != self.active_font { - // If we will change the font, first write the remaining things. - self.write_buffered_text(); - self.set_font(font); - } - - self.buffered_text.push(c); - self.current_width += width; - } - } - - /// Move to the next line. A factor of 1.0 uses the default line spacing. - fn move_newline(&mut self, factor: f32) { - let vertical = Size::from_points(self.layouter.ctx.text_style.font_size) - * self.layouter.ctx.text_style.line_spacing - * factor; - - self.actions.push(TextAction::MoveNewline(Position { - x: Size::zero(), - y: vertical - })); - - self.current_width = Size::zero(); - } - - /// Output a text action containing the buffered text and reset the buffer. - fn write_buffered_text(&mut self) { - if !self.buffered_text.is_empty() { - let mut buffered = String::new(); - mem::swap(&mut self.buffered_text, &mut buffered); - self.actions.push(TextAction::WriteText(buffered)); - } - } - - /// Output an action setting a new font and update the active font. - fn set_font(&mut self, index: usize) { - self.active_font = index; - self.actions.push(TextAction::SetFont(index, self.layouter.ctx.text_style.font_size)); - } - - /// Check whether additional text with the given width would overflow the current line. - fn would_overflow(&self, width: Size) -> bool { - self.current_width + width > self.max_width - } -} diff --git a/src/lib.rs b/src/lib.rs index 398df4670..08156e14a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,9 +48,9 @@ use std::fmt::{self, Debug, Formatter}; use crate::doc::Document; use crate::font::{Font, FontLoader, FontProvider}; use crate::func::Scope; -use crate::layout::{layout, Layout, Layouter, LayoutContext, BoxLayouter, Extent, Position}; -use crate::layout::{PageStyle, TextStyle, LayoutResult, LayoutError}; use crate::parsing::{parse, ParseContext, ParseResult, ParseError}; +use crate::layout::{layout, LayoutContext, LayoutSpace, LayoutError, LayoutResult, BoxLayout}; +use crate::style::{PageStyle, TextStyle}; use crate::syntax::SyntaxTree; #[macro_use] @@ -62,6 +62,8 @@ pub mod font; pub mod func; pub mod layout; pub mod parsing; +pub mod size; +pub mod style; pub mod syntax; @@ -69,12 +71,12 @@ pub mod syntax; /// /// Can be configured through various methods. pub struct Typesetter<'p> { - /// The default page style. - page_style: PageStyle, - /// The default text style. - text_style: TextStyle, /// Font providers. font_providers: Vec>, + /// The default text style. + text_style: TextStyle, + /// The default page style. + page_style: PageStyle, } impl<'p> Typesetter<'p> { @@ -82,8 +84,8 @@ impl<'p> Typesetter<'p> { #[inline] pub fn new() -> Typesetter<'p> { Typesetter { - page_style: PageStyle::default(), text_style: TextStyle::default(), + page_style: PageStyle::default(), font_providers: vec![], } } @@ -115,36 +117,19 @@ impl<'p> Typesetter<'p> { } /// Layout a syntax tree and return the layout and the referenced font list. - pub fn layout(&self, tree: &SyntaxTree) -> LayoutResult<(Layout, Vec)> { + pub fn layout(&self, tree: &SyntaxTree) -> LayoutResult<(BoxLayout, Vec)> { let loader = FontLoader::new(&self.font_providers); - - // Prepare the layouting context. - let page = &self.page_style; - let mut ctx = LayoutContext { + let ctx = LayoutContext { loader: &loader, - text_style: self.text_style.clone(), - max_extent: Extent { - width: page.width - page.margin_left - page.margin_right, - height: page.height - page.margin_top - page.margin_bottom, + style: self.text_style.clone(), + space: LayoutSpace { + dimensions: self.page_style.dimensions, + padding: self.page_style.margins, }, }; - // Layout the content of the page (without margins). - let content = layout(&tree, &ctx)?; - - // Adjust the context for adding the margins. - ctx.max_extent = Extent { - width: page.width, - height: page.height, - }; - - // Add the margins. - let mut box_layouter = BoxLayouter::new(&ctx); - let start = Position { x: page.margin_left, y: page.margin_top }; - box_layouter.add_layout_absolute(start, content); - let layout = box_layouter.finish()?; - - Ok((layout, loader.into_fonts())) + let pages = layout(&tree, &ctx)?; + Ok((pages, loader.into_fonts())) } /// Typeset a portable document from source code. @@ -152,7 +137,7 @@ impl<'p> Typesetter<'p> { pub fn typeset(&self, src: &str) -> Result { let tree = self.parse(src)?; let (layout, fonts) = self.layout(&tree)?; - let document = layout.into_document(fonts); + let document = layout.into_doc(fonts); Ok(document) } } diff --git a/src/size.rs b/src/size.rs new file mode 100644 index 000000000..d10961644 --- /dev/null +++ b/src/size.rs @@ -0,0 +1,307 @@ +//! General spacing types. + +use std::cmp::Ordering; +use std::fmt::{self, Display, Debug, Formatter}; +use std::iter::Sum; +use std::ops::*; + + +/// A general spacing type. +#[derive(Copy, Clone, PartialEq, Default)] +pub struct Size { + /// The size in typographic points (1/72 inches). + points: f32, +} + +/// A position or extent in 2-dimensional space. +#[derive(Copy, Clone, PartialEq, PartialOrd, Default)] +pub struct Size2D { + /// The horizontal coordinate. + pub x: Size, + /// The vertical coordinate. + pub y: Size, +} + +/// A size in four directions. +#[derive(Copy, Clone, PartialEq, PartialOrd, Default)] +pub struct SizeBox { + /// The left extent. + pub left: Size, + /// The top extent. + pub top: Size, + /// The right extent. + pub right: Size, + /// The bottom extent. + pub bottom: Size, +} + +impl Size { + /// Create a zeroed size. + #[inline] + pub fn zero() -> Size { Size { points: 0.0 } } + + /// Create a size from an amount of points. + #[inline] + pub fn from_points(points: f32) -> Size { Size { points } } + + /// Create a size from an amount of inches. + #[inline] + pub fn from_inches(inches: f32) -> Size { Size { points: 72.0 * inches } } + + /// Create a size from an amount of millimeters. + #[inline] + pub fn from_mm(mm: f32) -> Size { Size { points: 2.83465 * mm } } + + /// Create a size from an amount of centimeters. + #[inline] + pub fn from_cm(cm: f32) -> Size { Size { points: 28.3465 * cm } } + + /// Convert this size into points. + #[inline] + pub fn to_points(&self) -> f32 { self.points } + + /// Convert this size into inches. + #[inline] + pub fn to_inches(&self) -> f32 { self.points * 0.0138889 } + + /// Convert this size into millimeters. + #[inline] + pub fn to_mm(&self) -> f32 { self.points * 0.352778 } + + /// Convert this size into centimeters. + #[inline] + pub fn to_cm(&self) -> f32 { self.points * 0.0352778 } +} + +impl Size2D { + /// Create a new 2D vector from two sizes. + #[inline] + pub fn new(x: Size, y: Size) -> Size2D { Size2D { x, y } } + + /// Create a zeroed vector. + #[inline] + pub fn zero() -> Size2D { Size2D { x: Size::zero(), y: Size::zero() } } +} + +impl SizeBox { + /// Create a new box from four sizes. + #[inline] + pub fn new(left: Size, top: Size, right: Size, bottom: Size) -> SizeBox { + SizeBox { left, top, right, bottom } + } + + /// Create a zeroed vector. + #[inline] + pub fn zero() -> SizeBox { + SizeBox { + left: Size::zero(), + top: Size::zero(), + right: Size::zero(), + bottom: Size::zero(), + } + } +} + +impl Display for Size { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}pt", self.points) + } +} + +impl Debug for Size { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl PartialOrd for Size { + #[inline] + fn partial_cmp(&self, other: &Size) -> Option { + self.points.partial_cmp(&other.points) + } +} + +impl Neg for Size { + type Output = Size; + + #[inline] + fn neg(self) -> Size { + Size { points: -self.points } + } +} + +impl Sum for Size { + #[inline] + fn sum(iter: I) -> Size where I: Iterator { + iter.fold(Size::zero(), Add::add) + } +} + +macro_rules! impl_reflexive { + ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident) => { + impl $trait for Size { + type Output = Size; + + #[inline] + fn $func(self, other: Size) -> Size { + Size { points: $trait::$func(self.points, other.points) } + } + } + + impl $assign_trait for Size { + #[inline] + fn $assign_func(&mut self, other: Size) { + $assign_trait::$assign_func(&mut self.points, other.points); + } + } + }; +} + +macro_rules! impl_num_back { + ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => { + impl $trait<$ty> for Size { + type Output = Size; + + #[inline] + fn $func(self, other: $ty) -> Size { + Size { points: $trait::$func(self.points, other as f32) } + } + } + + impl $assign_trait<$ty> for Size { + #[inline] + fn $assign_func(&mut self, other: $ty) { + $assign_trait::$assign_func(&mut self.points, other as f32); + } + } + }; +} + +macro_rules! impl_num_both { + ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => { + impl_num_back!($trait, $func, $assign_trait, $assign_func, $ty); + + impl $trait for $ty { + type Output = Size; + + #[inline] + fn $func(self, other: Size) -> Size { + Size { points: $trait::$func(self as f32, other.points) } + } + } + }; +} + +impl_reflexive!(Add, add, AddAssign, add_assign); +impl_reflexive!(Sub, sub, SubAssign, sub_assign); +impl_num_both!(Mul, mul, MulAssign, mul_assign, f32); +impl_num_both!(Mul, mul, MulAssign, mul_assign, i32); +impl_num_back!(Div, div, DivAssign, div_assign, f32); +impl_num_back!(Div, div, DivAssign, div_assign, i32); + +impl Display for Size2D { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "[{}, {}]", self.x, self.y) + } +} + +impl Debug for Size2D { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl Neg for Size2D { + type Output = Size2D; + + #[inline] + fn neg(self) -> Size2D { + Size2D { x: -self.x, y: -self.y } + } +} + +macro_rules! impl_reflexive2d { + ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident) => { + impl $trait for Size2D { + type Output = Size2D; + + #[inline] + fn $func(self, other: Size2D) -> Size2D { + Size2D { + x: $trait::$func(self.x, other.x), + y: $trait::$func(self.y, other.y), + } + } + } + + impl $assign_trait for Size2D { + #[inline] + fn $assign_func(&mut self, other: Size2D) { + $assign_trait::$assign_func(&mut self.x, other.x); + $assign_trait::$assign_func(&mut self.y, other.y); + } + } + }; +} + +macro_rules! impl_num_back2d { + ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => { + impl $trait<$ty> for Size2D { + type Output = Size2D; + + #[inline] + fn $func(self, other: $ty) -> Size2D { + Size2D { + x: $trait::$func(self.x, other as f32), + y: $trait::$func(self.y, other as f32), + } + } + } + + impl $assign_trait<$ty> for Size2D { + #[inline] + fn $assign_func(&mut self, other: $ty) { + $assign_trait::$assign_func(&mut self.x, other as f32); + $assign_trait::$assign_func(&mut self.y, other as f32); + } + } + }; +} + +macro_rules! impl_num_both2d { + ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => { + impl_num_back2d!($trait, $func, $assign_trait, $assign_func, $ty); + + impl $trait for $ty { + type Output = Size2D; + + #[inline] + fn $func(self, other: Size2D) -> Size2D { + Size2D { + x: $trait::$func(self as f32, other.x), + y: $trait::$func(self as f32, other.y), + } + } + } + }; +} + +impl_reflexive2d!(Add, add, AddAssign, add_assign); +impl_reflexive2d!(Sub, sub, SubAssign, sub_assign); +impl_num_both2d!(Mul, mul, MulAssign, mul_assign, f32); +impl_num_both2d!(Mul, mul, MulAssign, mul_assign, i32); +impl_num_back2d!(Div, div, DivAssign, div_assign, f32); +impl_num_back2d!(Div, div, DivAssign, div_assign, i32); + +impl Display for SizeBox { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "[left: {}, top: {}, right: {}, bottom: {}]", + self.left, self.top, self.right, self.bottom) + } +} + +impl Debug for SizeBox { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} diff --git a/src/style.rs b/src/style.rs new file mode 100644 index 000000000..c4e92ec9c --- /dev/null +++ b/src/style.rs @@ -0,0 +1,60 @@ +//! Styles for layouting. + +use crate::font::FontFamily; +use crate::size::{Size, Size2D, SizeBox}; + + +/// Default styles for text. +#[derive(Debug, Clone)] +pub struct TextStyle { + /// A fallback list of font families to use. + pub font_families: Vec, + /// The font size. + pub font_size: f32, + /// The line spacing (as a multiple of the font size). + pub line_spacing: f32, + /// The paragraphs spacing (as a multiple of the line spacing). + pub paragraph_spacing: f32, +} + +impl Default for TextStyle { + fn default() -> TextStyle { + use FontFamily::*; + TextStyle { + // Default font family, font size and line spacing. + font_families: vec![SansSerif, Serif, Monospace], + font_size: 11.0, + line_spacing: 1.25, + paragraph_spacing: 1.5, + } + } +} + +/// Default styles for pages. +#[derive(Debug, Clone)] +pub struct PageStyle { + /// Width and height of the page. + pub dimensions: Size2D, + /// The amount of white space on each side. + pub margins: SizeBox, +} + +impl Default for PageStyle { + fn default() -> PageStyle { + PageStyle { + // A4 paper. + dimensions: Size2D { + x: Size::from_mm(210.0), + y: Size::from_mm(297.0), + }, + + // All the same margins. + margins: SizeBox { + left: Size::from_cm(3.0), + top: Size::from_cm(3.0), + right: Size::from_cm(3.0), + bottom: Size::from_cm(3.0), + }, + } + } +}