diff options
Diffstat (limited to 'src/layout')
| -rw-r--r-- | src/layout/boxed.rs | 54 | ||||
| -rw-r--r-- | src/layout/flex.rs | 61 | ||||
| -rw-r--r-- | src/layout/mod.rs | 174 | ||||
| -rw-r--r-- | src/layout/size.rs | 173 | ||||
| -rw-r--r-- | src/layout/text.rs | 243 |
5 files changed, 169 insertions, 536 deletions
diff --git a/src/layout/boxed.rs b/src/layout/boxed.rs index c240db0b..b75ea75a 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<TextAction>, +} + +impl BoxLayout { + /// Convert this layout into a document given the list of fonts referenced by it. + pub fn into_doc(self, fonts: Vec<Font>) -> 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<Layout> { - 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 00000000..faddc95a --- /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<BoxLayout>, +} + +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<BoxLayout>, +} + +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 1c1f743e..8b0c3004 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<Layout> { - let mut layouter = TextLayouter::new(ctx); - - let mut italic = false; - let mut bold = false; - - 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); - } +/// Types that layout components and can be finished into some kind of layout. +pub trait Layouter { + type Layout; - Node::Func(_) => unimplemented!(), - } - } + /// Finish the layouting and create the layout from this. + fn finish(self) -> Self::Layout; - 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<TextAction>, -} - -impl Layout { - /// Convert this layout into a document given the list of fonts referenced by it. - pub fn into_document(self, fonts: Vec<Font>) -> 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<Layout>; +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<FontFamily>, - /// 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<BoxLayout> { + // The top-level layouter and the sub-level layouter. + let mut box_layouter = BoxLayouter::new(ctx); + let mut flex_layouter = FlexLayouter::new(ctx); -/// 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, -} + // The current text style. + let mut italic = false; + let mut bold = false; -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), + // 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!() + } } } + + // 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 92a4c113..00000000 --- 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<Ordering> { - 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<I>(iter: I) -> Size where I: Iterator<Item=Size> { - 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<Size> 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 d1e2afd7..00000000 --- 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<Unit>, - 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<Font>)> { - 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<Layout> { - 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<TextAction>, - 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<Layout> { - // 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 - } -} |
