From 968e121697a96a2e3b05a560176c34f4bb6693c3 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 21 Jun 2019 21:37:29 +0200 Subject: [PATCH] =?UTF-8?q?Implement=20flex=20and=20box=20layouting=20?= =?UTF-8?q?=F0=9F=93=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/font.rs | 2 +- src/layout/boxed.rs | 104 +++++++++++++++++++------ src/layout/flex.rs | 170 +++++++++++++++++++++++++++++++++-------- src/layout/mod.rs | 180 +++++++++++++++++++++++++++++++++----------- src/layout/text.rs | 62 +++++++++++++++ src/lib.rs | 9 ++- src/size.rs | 58 +++++++++----- src/style.rs | 20 +++-- 8 files changed, 478 insertions(+), 127 deletions(-) create mode 100644 src/layout/text.rs diff --git a/src/font.rs b/src/font.rs index fada64efc..9b13ed326 100644 --- a/src/font.rs +++ b/src/font.rs @@ -60,7 +60,7 @@ impl Font { // Create a conversion function between font units and sizes. let font_unit_ratio = 1.0 / (head.units_per_em as f32); - let font_unit_to_size = |x| Size::from_points(font_unit_ratio * x as f32); + let font_unit_to_size = |x| Size::points(font_unit_ratio * x as f32); // Find out the name of the font. let font_name = name.get_decoded(NameEntry::PostScriptName) diff --git a/src/layout/boxed.rs b/src/layout/boxed.rs index b75ea75ad..2ee2dbdc7 100644 --- a/src/layout/boxed.rs +++ b/src/layout/boxed.rs @@ -1,17 +1,18 @@ -//! Definitive layouting of boxes. +//! Block-style layouting of boxes. use crate::doc::{Document, Page, TextAction}; use crate::font::Font; -use super::{Layouter, LayoutContext, Size2D}; +use crate::size::{Size, Size2D}; +use super::LayoutSpace; -/// A box layout has a fixed width and height and consists of actions. +/// A box layout has a fixed width and height and composes of actions. #[derive(Debug, Clone)] pub struct BoxLayout { /// The size of the box. - dimensions: Size2D, + pub dimensions: Size2D, /// The actions composing this layout. - actions: Vec, + pub actions: Vec, } impl BoxLayout { @@ -28,25 +29,68 @@ impl BoxLayout { } } -/// Layouts boxes block-style. -#[derive(Debug)] -pub struct BoxLayouter<'a, 'p> { - ctx: &'a LayoutContext<'a, 'p>, - actions: Vec, +/// The context for layouting boxes. +#[derive(Debug, Copy, Clone)] +pub struct BoxContext { + /// The space to layout the boxes in. + pub space: LayoutSpace, } -impl<'a, 'p> BoxLayouter<'a, 'p> { +/// Layouts boxes block-style. +#[derive(Debug)] +pub struct BoxLayouter { + ctx: BoxContext, + actions: Vec, + dimensions: Size2D, + usable: Size2D, + cursor: Size2D, +} + +impl BoxLayouter { /// Create a new box layouter. - pub fn new(ctx: &'a LayoutContext<'a, 'p>) -> BoxLayouter<'a, 'p> { + pub fn new(ctx: BoxContext) -> BoxLayouter { + let space = ctx.space; BoxLayouter { ctx, actions: vec![], + dimensions: Size2D::zero(), + usable: space.usable(), + cursor: Size2D::new(space.padding.left, space.padding.right), } } /// Add a sublayout. pub fn add_box(&mut self, layout: BoxLayout) { - unimplemented!() + // In the flow direction (vertical) add the layout and in the second + // direction just consider the maximal size of any child layout. + let new = Size2D { + x: crate::size::max(self.dimensions.x, layout.dimensions.x), + y: self.dimensions.y + layout.dimensions.y, + }; + + if self.overflows(new) { + panic!("box layouter: would overflow in add_box"); + } + + // Apply the dimensions because they fit. + self.dimensions = new; + + // Move all actions into this layout and translate absolute positions. + self.actions.push(TextAction::MoveAbsolute(self.cursor)); + self.actions.extend(super::translate_actions(self.cursor, layout.actions)); + + // Adjust the cursor. + self.cursor.y += layout.dimensions.y; + } + + /// Add some space in between two boxes. + pub fn add_space(&mut self, space: Size) { + if self.overflows(self.dimensions + Size2D::with_y(space)) { + panic!("box layouter: would overflow in add_space"); + } + + self.cursor.y += space; + self.dimensions.y += space; } /// Add a sublayout at an absolute position. @@ -54,20 +98,34 @@ impl<'a, 'p> BoxLayouter<'a, 'p> { self.actions.push(TextAction::MoveAbsolute(position)); self.actions.extend(layout.actions); } -} -impl Layouter for BoxLayouter<'_, '_> { - type Layout = BoxLayout; + /// Whether this layouter contains any items. + pub fn is_empty(&self) -> bool { + self.actions.is_empty() + } - /// Finish the layouting and create a box layout from this. - fn finish(self) -> BoxLayout { - BoxLayout { - dimensions: self.ctx.space.dimensions.clone(), - actions: self.actions + /// The remaining space for new boxes. + pub fn remaining(&self) -> Size2D { + Size2D { + x: self.usable.x, + y: self.usable.y - self.dimensions.y, } } - fn is_empty(&self) -> bool { - self.actions.is_empty() + /// Finish the layouting and create a box layout from this. + pub fn finish(self) -> BoxLayout { + BoxLayout { + dimensions: if self.ctx.space.shrink_to_fit { + self.dimensions.padded(self.ctx.space.padding) + } else { + self.ctx.space.dimensions + }, + actions: self.actions, + } + } + + /// Whether the given box is bigger than what we can hold. + fn overflows(&self, dimensions: Size2D) -> bool { + dimensions.x > self.usable.x || dimensions.y > self.usable.y } } diff --git a/src/layout/flex.rs b/src/layout/flex.rs index faddc95a6..924ebec50 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -1,61 +1,171 @@ //! Flexible and lazy layouting of boxes. -use super::{Layouter, LayoutContext, BoxLayout}; +use crate::doc::TextAction; +use crate::size::Size2D; +use super::{LayoutSpace, 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, + pub units: Vec, + /// The layout space to arrange in. + pub ctx: FlexContext, +} + +/// A unit in a flex layout. +#[derive(Debug, Clone)] +pub enum FlexUnit { + /// A content unit to be arranged flexibly. + Boxed(BoxLayout), + /// A unit which acts as glue between two [`FlexUnit::Boxed`] units and + /// is only present if there was no flow break in between the two surrounding boxes. + Glue(BoxLayout), } 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 { + pub fn new(ctx: FlexContext) -> FlexLayout { + FlexLayout { ctx, - layouts: vec![], + units: vec![], } } /// Add a sublayout. pub fn add_box(&mut self, layout: BoxLayout) { - self.layouts.push(layout); + self.units.push(FlexUnit::Boxed(layout)); + } + + /// Add a glue layout which can be replaced by a line break. + pub fn add_glue(&mut self, glue: BoxLayout) { + self.units.push(FlexUnit::Glue(glue)); } /// Add all sublayouts of another flex layout. pub fn add_flexible(&mut self, layout: FlexLayout) { - self.layouts.extend(layout.layouts); + self.units.extend(layout.units); + } + + /// Whether this layouter contains any items. + pub fn is_empty(&self) -> bool { + self.units.is_empty() + } + + /// Compute the justified layout. + pub fn into_box(self) -> BoxLayout { + FlexFinisher::new(self).finish() } } -impl Layouter for FlexLayouter<'_, '_> { - type Layout = FlexLayout; +/// The context for flex layouting. +#[derive(Debug, Copy, Clone)] +pub struct FlexContext { + /// The space to layout the boxes in. + pub space: LayoutSpace, + /// The flex spacing (like line spacing). + pub flex_spacing: f32, +} - /// Finish the layouting and create a flexible layout from this. - fn finish(self) -> FlexLayout { - FlexLayout { - layouts: self.layouts +/// Finishes a flex layout by justifying the positions of the individual boxes. +#[derive(Debug)] +struct FlexFinisher { + units: Vec, + ctx: FlexContext, + actions: Vec, + dimensions: Size2D, + usable: Size2D, + cursor: Size2D, + line: Size2D, +} + +impl FlexFinisher { + /// Create the finisher from the layout. + fn new(layout: FlexLayout) -> FlexFinisher { + let space = layout.ctx.space; + FlexFinisher { + units: layout.units, + ctx: layout.ctx, + actions: vec![], + dimensions: Size2D::zero(), + usable: space.usable(), + cursor: Size2D::new(space.padding.left, space.padding.top), + line: Size2D::zero(), } } - fn is_empty(&self) -> bool { - self.layouts.is_empty() + /// Finish the flex layout into the justified box layout. + fn finish(mut self) -> BoxLayout { + // Move the units out of the layout. + let units = self.units; + self.units = vec![]; + + // Arrange the units. + for unit in units { + match unit { + FlexUnit::Boxed(boxed) => self.boxed(boxed), + FlexUnit::Glue(glue) => self.glue(glue), + } + } + + // Flush everything to get the correct dimensions. + self.newline(); + + BoxLayout { + dimensions: if self.ctx.space.shrink_to_fit { + self.dimensions.padded(self.ctx.space.padding) + } else { + self.ctx.space.dimensions + }, + actions: self.actions, + } + } + + /// Layout the box. + fn boxed(&mut self, boxed: BoxLayout) { + // Move to the next line if necessary. + if self.line.x + boxed.dimensions.x > self.usable.x { + // If it still does not fit, we stand no chance. + if boxed.dimensions.x > self.usable.x { + panic!("flex layouter: box is to wide"); + } + + self.newline(); + } + + self.append(boxed); + } + + /// Layout the glue. + fn glue(&mut self, glue: BoxLayout) { + // Only add the glue if it fits on the line, otherwise move to the next line. + if self.line.x + glue.dimensions.x > self.usable.x { + self.newline(); + } else { + self.append(glue); + } + } + + /// Append a box to the layout without checking anything. + fn append(&mut self, layout: BoxLayout) { + // Move all actions into this layout and translate absolute positions. + self.actions.push(TextAction::MoveAbsolute(self.cursor)); + self.actions.extend(super::translate_actions(self.cursor, layout.actions)); + + // Adjust the sizes. + self.line.x += layout.dimensions.x; + self.line.y = crate::size::max(self.line.y, layout.dimensions.y); + self.cursor.x += layout.dimensions.x; + } + + /// Move to the next line. + fn newline(&mut self) { + self.line.y *= self.ctx.flex_spacing; + self.dimensions.x = crate::size::max(self.dimensions.x, self.line.x); + self.dimensions.y += self.line.y; + self.cursor.x = self.ctx.space.padding.left; + self.cursor.y += self.line.y; + self.line = Size2D::zero(); } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 8b0c30041..64eeb00a2 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,28 +1,20 @@ //! The layouting engine. +use crate::doc::TextAction; use crate::font::{FontLoader, FontError}; -use crate::size::{Size2D, SizeBox}; +use crate::size::{Size, Size2D, SizeBox}; use crate::syntax::{SyntaxTree, Node}; use crate::style::TextStyle; -mod boxed; -mod flex; +use self::flex::{FlexLayout, FlexContext}; +use self::boxed::{BoxLayout, BoxContext, BoxLayouter}; +use self::text::TextContext; -pub use flex::{FlexLayout, FlexLayouter}; -pub use boxed::{BoxLayout, BoxLayouter}; +pub mod text; +pub mod boxed; +pub mod flex; -/// Types that layout components and can be finished into some kind of layout. -pub trait Layouter { - type Layout; - - /// Finish the layouting and create the layout from this. - fn finish(self) -> Self::Layout; - - /// Whether this layouter contains any items. - fn is_empty(&self) -> bool; -} - /// A collection of layouted content. #[derive(Debug, Clone)] pub enum Layout { @@ -44,54 +36,152 @@ pub struct LayoutContext<'a, 'p> { } /// Spacial constraints for layouting. -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone)] 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, + /// Whether to shrink the dimensions to fit the content or the keep the + /// original ones. + pub shrink_to_fit: bool, +} + +impl LayoutSpace { + /// The actually usable area. + pub fn usable(&self) -> Size2D { + Size2D { + x: self.dimensions.x - self.padding.left - self.padding.right, + y: self.dimensions.y - self.padding.top - self.padding.bottom, + } + } } /// 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); + Layouter::new(tree, ctx).layout() +} - // The current text style. - let mut italic = false; - let mut bold = false; +/// Transforms a syntax tree into a box layout. +#[derive(Debug)] +struct Layouter<'a, 'p> { + tree: &'a SyntaxTree, + box_layouter: BoxLayouter, + flex_layout: FlexLayout, + flex_ctx: FlexContext, + text_ctx: TextContext<'a, 'p>, +} - // Walk all nodes and layout them. - for node in &tree.nodes { - match node { - Node::Text(text) => { - unimplemented!() - }, - Node::Space => { - unimplemented!() - }, - Node::Newline => { - unimplemented!() +impl<'a, 'p> Layouter<'a, 'p> { + /// Create a new layouter. + fn new(tree: &'a SyntaxTree, ctx: &LayoutContext<'a, 'p>) -> Layouter<'a, 'p> { + // The top-level context for arranging paragraphs. + let box_ctx = BoxContext { space: ctx.space }; + + // The sub-level context for arranging pieces of text. + let flex_ctx = FlexContext { + space: LayoutSpace { + dimensions: ctx.space.usable(), + padding: SizeBox::zero(), + shrink_to_fit: true, }, + flex_spacing: ctx.style.line_spacing, + }; - // Toggle the text styles. - Node::ToggleItalics => italic = !italic, - Node::ToggleBold => bold = !bold, + // The mutable context for layouting single pieces of text. + let text_ctx = TextContext { + loader: &ctx.loader, + style: ctx.style.clone(), + }; - Node::Func(func) => { - unimplemented!() - } + Layouter { + tree, + box_layouter: BoxLayouter::new(box_ctx), + flex_layout: FlexLayout::new(flex_ctx), + flex_ctx, + text_ctx, } } - // 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); + /// Layout the tree into a box. + fn layout(mut self) -> LayoutResult { + // Walk all nodes and layout them. + for node in &self.tree.nodes { + match node { + // Layout a single piece of text. + Node::Text(text) => { + let boxed = self::text::layout(text, &self.text_ctx)?; + self.flex_layout.add_box(boxed); + }, + Node::Space => { + if !self.flex_layout.is_empty() { + let boxed = self::text::layout(" ", &self.text_ctx)?; + self.flex_layout.add_glue(boxed); + } + }, + + // Finish the current flex layout and add it to the box layouter. + // Then start a new flex layouting process. + Node::Newline => { + // Finish the current paragraph into a box and add it. + self.add_paragraph_spacing(); + let boxed = self.flex_layout.into_box(); + self.box_layouter.add_box(boxed); + + // Create a fresh flex layout for the next paragraph. + self.flex_ctx.space.dimensions = self.box_layouter.remaining(); + self.flex_layout = FlexLayout::new(self.flex_ctx); + }, + + // Toggle the text styles. + Node::ToggleItalics => self.text_ctx.style.italic = !self.text_ctx.style.italic, + Node::ToggleBold => self.text_ctx.style.bold = !self.text_ctx.style.bold, + + // Execute a function. + Node::Func(_) => unimplemented!(), + } + } + + // If there are remainings, add them to the layout. + if !self.flex_layout.is_empty() { + self.add_paragraph_spacing(); + let boxed = self.flex_layout.into_box(); + self.box_layouter.add_box(boxed); + } + + Ok(self.box_layouter.finish()) } - Ok(box_layouter.finish()) + /// Add the spacing between two paragraphs. + fn add_paragraph_spacing(&mut self) { + let size = Size::points(self.text_ctx.style.font_size) + * (self.text_ctx.style.line_spacing * self.text_ctx.style.paragraph_spacing - 1.0); + self.box_layouter.add_space(size); + } +} + +/// Translate a stream of text actions by an offset. +pub fn translate_actions(offset: Size2D, actions: I) -> TranslatedActions + where I: IntoIterator { + TranslatedActions { offset, iter: actions.into_iter() } +} + +/// An iterator over the translated text actions, created by [`translate_actions`]. +pub struct TranslatedActions where I: Iterator { + offset: Size2D, + iter: I, +} + +impl Iterator for TranslatedActions where I: Iterator { + type Item = TextAction; + + fn next(&mut self) -> Option { + use TextAction::*; + self.iter.next().map(|action| match action { + MoveAbsolute(pos) => MoveAbsolute(pos + self.offset), + a => a, + }) + } } /// The error type for layouting. diff --git a/src/layout/text.rs b/src/layout/text.rs new file mode 100644 index 000000000..8aa76c4cd --- /dev/null +++ b/src/layout/text.rs @@ -0,0 +1,62 @@ +//! Layouting of text into boxes. + +use crate::doc::TextAction; +use crate::font::FontQuery; +use crate::size::{Size, Size2D}; +use super::*; + + +/// The context for text layouting. +#[derive(Debug, Clone)] +pub struct TextContext<'a, 'p> { + /// Loads fonts matching queries. + pub loader: &'a FontLoader<'p>, + /// Base style to set text with. + pub style: TextStyle, +} + +/// Layout one piece of text without any breaks as one continous box. +pub fn layout(text: &str, ctx: &TextContext) -> LayoutResult { + let mut actions = Vec::new(); + let mut active_font = std::usize::MAX; + let mut buffer = String::new(); + let mut width = Size::zero(); + + // Walk the characters. + for character in text.chars() { + // Retrieve the best font for this character. + let (index, font) = ctx.loader.get(FontQuery { + families: ctx.style.font_families.clone(), + italic: ctx.style.italic, + bold: ctx.style.bold, + character, + }).ok_or_else(|| LayoutError::NoSuitableFont(character))?; + + // Add the char width to the total box width. + let char_width = font.widths[font.map(character) as usize] * ctx.style.font_size; + width += char_width; + + // Change the font if necessary. + if active_font != index { + if !buffer.is_empty() { + actions.push(TextAction::WriteText(buffer)); + buffer = String::new(); + } + + actions.push(TextAction::SetFont(index, ctx.style.font_size)); + active_font = index; + } + + buffer.push(character); + } + + // Write the remaining characters. + if !buffer.is_empty() { + actions.push(TextAction::WriteText(buffer)); + } + + Ok(BoxLayout { + dimensions: Size2D::new(width, Size::points(ctx.style.font_size)), + actions, + }) +} diff --git a/src/lib.rs b/src/lib.rs index 08156e14a..334f20b96 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,11 +26,10 @@ //! // (two sans-serif fonts and a fallback for the emoji). //! let mut typesetter = Typesetter::new(); //! typesetter.add_font_provider(FileSystemFontProvider::new("../fonts", vec![ -//! ("CMU-SansSerif-Regular.ttf", font_info!(["Computer Modern", SansSerif])), -//! ("CMU-SansSerif-Italic.ttf", font_info!(["Computer Modern", SansSerif], italic)), +//! ("CMU-Serif-Regular.ttf", font_info!(["Computer Modern", Serif])), +//! ("CMU-Serif-Italic.ttf", font_info!(["Computer Modern", Serif], italic)), //! ("NotoEmoji-Regular.ttf", font_info!(["NotoEmoji", "Noto", SansSerif, Serif, Monospace])), //! ])); -//! //! // Typeset the source code into a document. //! let document = typesetter.typeset(src).unwrap(); //! @@ -49,7 +48,8 @@ use crate::doc::Document; use crate::font::{Font, FontLoader, FontProvider}; use crate::func::Scope; use crate::parsing::{parse, ParseContext, ParseResult, ParseError}; -use crate::layout::{layout, LayoutContext, LayoutSpace, LayoutError, LayoutResult, BoxLayout}; +use crate::layout::{layout, LayoutContext, LayoutSpace, LayoutError, LayoutResult}; +use crate::layout::boxed::BoxLayout; use crate::style::{PageStyle, TextStyle}; use crate::syntax::SyntaxTree; @@ -125,6 +125,7 @@ impl<'p> Typesetter<'p> { space: LayoutSpace { dimensions: self.page_style.dimensions, padding: self.page_style.margins, + shrink_to_fit: false, }, }; diff --git a/src/size.rs b/src/size.rs index d10961644..e1f54a4ff 100644 --- a/src/size.rs +++ b/src/size.rs @@ -14,7 +14,7 @@ pub struct Size { } /// A position or extent in 2-dimensional space. -#[derive(Copy, Clone, PartialEq, PartialOrd, Default)] +#[derive(Copy, Clone, PartialEq, Default)] pub struct Size2D { /// The horizontal coordinate. pub x: Size, @@ -23,7 +23,7 @@ pub struct Size2D { } /// A size in four directions. -#[derive(Copy, Clone, PartialEq, PartialOrd, Default)] +#[derive(Copy, Clone, PartialEq, Default)] pub struct SizeBox { /// The left extent. pub left: Size, @@ -38,23 +38,23 @@ pub struct SizeBox { impl Size { /// Create a zeroed size. #[inline] - pub fn zero() -> Size { Size { points: 0.0 } } + pub fn zero() -> Size { Size::default() } /// Create a size from an amount of points. #[inline] - pub fn from_points(points: f32) -> Size { Size { points } } + pub fn 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 } } + pub fn 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 } } + pub fn 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 } } + pub fn cm(cm: f32) -> Size { Size { points: 28.3465 * cm } } /// Convert this size into points. #[inline] @@ -74,13 +74,30 @@ impl Size { } impl Size2D { - /// Create a new 2D vector from two sizes. + /// Create a new vector from two sizes. #[inline] pub fn new(x: Size, y: Size) -> Size2D { Size2D { x, y } } - /// Create a zeroed vector. + /// Create a vector with all set to zero. #[inline] - pub fn zero() -> Size2D { Size2D { x: Size::zero(), y: Size::zero() } } + pub fn zero() -> Size2D { Size2D::default() } + + /// Create a new vector with `x` set to zero and `y` to a value. + #[inline] + pub fn with_x(x: Size) -> Size2D { Size2D { x, y: Size::zero() } } + + /// Create a new vector with `y` set to zero and `x` to a value. + #[inline] + pub fn with_y(y: Size) -> Size2D { Size2D { x: Size::zero(), y } } + + /// Return a [`Size2D`] padded by the paddings of the given box. + #[inline] + pub fn padded(&self, padding: SizeBox) -> Size2D { + Size2D { + x: self.x + padding.left + padding.right, + y: self.y + padding.top + padding.bottom, + } + } } impl SizeBox { @@ -90,18 +107,25 @@ impl SizeBox { SizeBox { left, top, right, bottom } } - /// Create a zeroed vector. + /// Create a box with all set to zero. #[inline] pub fn zero() -> SizeBox { - SizeBox { - left: Size::zero(), - top: Size::zero(), - right: Size::zero(), - bottom: Size::zero(), - } + SizeBox::default() } } +/// The maximum of two sizes. +pub fn max(a: Size, b: Size) -> Size { + if a >= b { a } else { b } +} + +/// The minimum of two sizes. +pub fn min(a: Size, b: Size) -> Size { + if a <= b { a } else { b } +} + +//------------------------------------------------------------------------------------------------// + impl Display for Size { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}pt", self.points) diff --git a/src/style.rs b/src/style.rs index c4e92ec9c..97f9552c7 100644 --- a/src/style.rs +++ b/src/style.rs @@ -9,6 +9,10 @@ use crate::size::{Size, Size2D, SizeBox}; pub struct TextStyle { /// A fallback list of font families to use. pub font_families: Vec, + /// Whether the font is in italics. + pub italic: bool, + /// Whether the font is bold. + pub bold: bool, /// The font size. pub font_size: f32, /// The line spacing (as a multiple of the font size). @@ -22,7 +26,9 @@ impl Default for TextStyle { use FontFamily::*; TextStyle { // Default font family, font size and line spacing. - font_families: vec![SansSerif, Serif, Monospace], + font_families: vec![Serif, SansSerif, Monospace], + italic: false, + bold: false, font_size: 11.0, line_spacing: 1.25, paragraph_spacing: 1.5, @@ -44,16 +50,16 @@ impl Default for PageStyle { PageStyle { // A4 paper. dimensions: Size2D { - x: Size::from_mm(210.0), - y: Size::from_mm(297.0), + x: Size::mm(210.0), + y: Size::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), + left: Size::cm(2.5), + top: Size::cm(2.5), + right: Size::cm(2.5), + bottom: Size::cm(2.5), }, } }