summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-06-17 10:08:16 +0200
committerLaurenz <laurmaedje@gmail.com>2019-06-17 10:08:16 +0200
commitb53ad6b1ec8b2fd05566a83c9b895f265e61d281 (patch)
tree1c6d590ead7180fbef12915cfcd04418c9ea7902
parent236ebab23a106ca817de527ce6b6440d3b66c150 (diff)
Introduce flex layouting 🎈
-rw-r--r--src/doc.rs6
-rw-r--r--src/export/pdf.rs2
-rw-r--r--src/font.rs2
-rw-r--r--src/layout/boxed.rs54
-rw-r--r--src/layout/flex.rs61
-rw-r--r--src/layout/mod.rs174
-rw-r--r--src/layout/size.rs173
-rw-r--r--src/layout/text.rs243
-rw-r--r--src/lib.rs51
-rw-r--r--src/size.rs307
-rw-r--r--src/style.rs60
11 files changed, 559 insertions, 574 deletions
diff --git a/src/doc.rs b/src/doc.rs
index 304b4936..f08332f6 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 c3f67afa..1c0d52b0 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 6b074e73..fada64ef 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 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
- }
-}
diff --git a/src/lib.rs b/src/lib.rs
index 398df467..08156e14 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<Box<dyn FontProvider + 'p>>,
+ /// 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<Font>)> {
+ pub fn layout(&self, tree: &SyntaxTree) -> LayoutResult<(BoxLayout, Vec<Font>)> {
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<Document, TypesetError> {
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 00000000..d1096164
--- /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<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);
+
+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<Size2D> 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 00000000..c4e92ec9
--- /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<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,
+}
+
+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),
+ },
+ }
+ }
+}