Introduce flex layouting 🎈

This commit is contained in:
Laurenz 2019-06-17 10:08:16 +02:00
parent 236ebab23a
commit b53ad6b1ec
11 changed files with 559 additions and 574 deletions

View File

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

View File

@ -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_.

View File

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

View File

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

61
src/layout/flex.rs Normal file
View File

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

View File

@ -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);
/// 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<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);
// 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.

View File

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

View File

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

View File

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

307
src/size.rs Normal file
View File

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

60
src/style.rs Normal file
View File

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