mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Introduce flex layouting 🎈
This commit is contained in:
parent
236ebab23a
commit
b53ad6b1ec
@ -1,7 +1,7 @@
|
|||||||
//! Representation of typesetted documents.
|
//! Representation of typesetted documents.
|
||||||
|
|
||||||
use crate::font::Font;
|
use crate::font::Font;
|
||||||
use crate::layout::{Size, Position};
|
use crate::size::{Size, Size2D};
|
||||||
|
|
||||||
|
|
||||||
/// A complete typesetted document, which can be exported.
|
/// A complete typesetted document, which can be exported.
|
||||||
@ -28,9 +28,9 @@ pub struct Page {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum TextAction {
|
pub enum TextAction {
|
||||||
/// Move to an absolute position.
|
/// Move to an absolute position.
|
||||||
MoveAbsolute(Position),
|
MoveAbsolute(Size2D),
|
||||||
/// Move from the _start_ of the current line by an (x, y) offset.
|
/// Move from the _start_ of the current line by an (x, y) offset.
|
||||||
MoveNewline(Position),
|
MoveNewline(Size2D),
|
||||||
/// Write text starting at the current position.
|
/// Write text starting at the current position.
|
||||||
WriteText(String),
|
WriteText(String),
|
||||||
/// Set the font by index and font size.
|
/// Set the font by index and font size.
|
||||||
|
@ -10,7 +10,7 @@ use pdf::font::{GlyphUnit, CMap, CMapEncoding, WidthRecord, FontStream};
|
|||||||
|
|
||||||
use crate::doc::{Document, Page as DocPage, TextAction};
|
use crate::doc::{Document, Page as DocPage, TextAction};
|
||||||
use crate::font::{Font, FontError};
|
use crate::font::{Font, FontError};
|
||||||
use crate::layout::Size;
|
use crate::size::Size;
|
||||||
|
|
||||||
|
|
||||||
/// Exports documents into _PDFs_.
|
/// Exports documents into _PDFs_.
|
||||||
|
@ -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::tables::{Header, Name, CharMap, MaximumProfile, HorizontalMetrics, Post, OS2};
|
||||||
use opentype::global::{MacStyleFlags, NameEntry};
|
use opentype::global::{MacStyleFlags, NameEntry};
|
||||||
|
|
||||||
use crate::layout::Size;
|
use crate::size::Size;
|
||||||
|
|
||||||
|
|
||||||
/// A loaded and parsed font program.
|
/// A loaded and parsed font program.
|
||||||
|
@ -1,10 +1,34 @@
|
|||||||
//! Layouting of layout boxes.
|
//! Definitive layouting of boxes.
|
||||||
|
|
||||||
use crate::doc::TextAction;
|
use crate::doc::{Document, Page, TextAction};
|
||||||
use super::{Layouter, Layout, LayoutContext, LayoutResult, Position};
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct BoxLayouter<'a, 'p> {
|
pub struct BoxLayouter<'a, 'p> {
|
||||||
ctx: &'a LayoutContext<'a, 'p>,
|
ctx: &'a LayoutContext<'a, 'p>,
|
||||||
@ -21,17 +45,29 @@ impl<'a, 'p> BoxLayouter<'a, 'p> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a sublayout.
|
/// 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.push(TextAction::MoveAbsolute(position));
|
||||||
self.actions.extend(layout.actions);
|
self.actions.extend(layout.actions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layouter for BoxLayouter<'_, '_> {
|
impl Layouter for BoxLayouter<'_, '_> {
|
||||||
fn finish(self) -> LayoutResult<Layout> {
|
type Layout = BoxLayout;
|
||||||
Ok(Layout {
|
|
||||||
extent: self.ctx.max_extent.clone(),
|
/// Finish the layouting and create a box layout from this.
|
||||||
|
fn finish(self) -> BoxLayout {
|
||||||
|
BoxLayout {
|
||||||
|
dimensions: self.ctx.space.dimensions.clone(),
|
||||||
actions: self.actions
|
actions: self.actions
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.actions.is_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
61
src/layout/flex.rs
Normal file
61
src/layout/flex.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
@ -1,74 +1,35 @@
|
|||||||
//! The layouting engine.
|
//! The layouting engine.
|
||||||
|
|
||||||
use crate::doc::{Document, Page, TextAction};
|
use crate::font::{FontLoader, FontError};
|
||||||
use crate::font::{Font, FontLoader, FontFamily, FontError};
|
use crate::size::{Size2D, SizeBox};
|
||||||
use crate::syntax::{SyntaxTree, Node};
|
use crate::syntax::{SyntaxTree, Node};
|
||||||
|
use crate::style::TextStyle;
|
||||||
|
|
||||||
mod size;
|
|
||||||
mod text;
|
|
||||||
mod boxed;
|
mod boxed;
|
||||||
|
mod flex;
|
||||||
|
|
||||||
pub use size::{Size, Position, Extent};
|
pub use flex::{FlexLayout, FlexLayouter};
|
||||||
pub use text::TextLayouter;
|
pub use boxed::{BoxLayout, BoxLayouter};
|
||||||
pub use boxed::BoxLayouter;
|
|
||||||
|
|
||||||
|
|
||||||
/// Layout a syntax tree in a given context.
|
/// Types that layout components and can be finished into some kind of layout.
|
||||||
pub fn layout(tree: &SyntaxTree, ctx: &LayoutContext) -> LayoutResult<Layout> {
|
pub trait Layouter {
|
||||||
let mut layouter = TextLayouter::new(ctx);
|
type Layout;
|
||||||
|
|
||||||
let mut italic = false;
|
/// Finish the layouting and create the layout from this.
|
||||||
let mut bold = false;
|
fn finish(self) -> Self::Layout;
|
||||||
|
|
||||||
for node in &tree.nodes {
|
/// Whether this layouter contains any items.
|
||||||
match node {
|
fn is_empty(&self) -> bool;
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A collection of layouted content.
|
/// A collection of layouted content.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Layout {
|
pub enum Layout {
|
||||||
/// The extent of this layout into all directions.
|
/// A box layout.
|
||||||
extent: Extent,
|
Boxed(BoxLayout),
|
||||||
/// Actions composing this layout.
|
/// A flexible layout.
|
||||||
actions: Vec<TextAction>,
|
Flex(FlexLayout),
|
||||||
}
|
|
||||||
|
|
||||||
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>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The context for layouting.
|
/// The context for layouting.
|
||||||
@ -76,70 +37,61 @@ pub trait Layouter {
|
|||||||
pub struct LayoutContext<'a, 'p> {
|
pub struct LayoutContext<'a, 'p> {
|
||||||
/// Loads fonts matching queries.
|
/// Loads fonts matching queries.
|
||||||
pub loader: &'a FontLoader<'p>,
|
pub loader: &'a FontLoader<'p>,
|
||||||
/// The spacial constraints to layout in.
|
|
||||||
pub max_extent: Extent,
|
|
||||||
/// Base style to set text with.
|
/// 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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TextStyle {
|
pub struct LayoutSpace {
|
||||||
/// A fallback list of font families to use.
|
/// The maximum size of the box to layout in.
|
||||||
pub font_families: Vec<FontFamily>,
|
pub dimensions: Size2D,
|
||||||
/// The font size.
|
/// Padding that should be respected on each side.
|
||||||
pub font_size: f32,
|
pub padding: SizeBox,
|
||||||
/// 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 {
|
/// Layout a syntax tree in a given context.
|
||||||
fn default() -> TextStyle {
|
pub fn layout(tree: &SyntaxTree, ctx: &LayoutContext) -> LayoutResult<BoxLayout> {
|
||||||
use FontFamily::*;
|
// The top-level layouter and the sub-level layouter.
|
||||||
TextStyle {
|
let mut box_layouter = BoxLayouter::new(ctx);
|
||||||
// Default font family, font size and line spacing.
|
let mut flex_layouter = FlexLayouter::new(ctx);
|
||||||
font_families: vec![SansSerif, Serif, Monospace],
|
|
||||||
font_size: 11.0,
|
// The current text style.
|
||||||
line_spacing: 1.25,
|
let mut italic = false;
|
||||||
paragraph_spacing: 1.5,
|
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.
|
// If there are remainings, add them to the layout.
|
||||||
#[derive(Debug, Clone)]
|
if !flex_layouter.is_empty() {
|
||||||
pub struct PageStyle {
|
let boxed = flex_layouter.finish().into_box();
|
||||||
/// The width of the page.
|
box_layouter.add_box(boxed);
|
||||||
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 {
|
Ok(box_layouter.finish())
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The error type for layouting.
|
/// The error type for layouting.
|
||||||
|
@ -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);
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
51
src/lib.rs
51
src/lib.rs
@ -48,9 +48,9 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use crate::doc::Document;
|
use crate::doc::Document;
|
||||||
use crate::font::{Font, FontLoader, FontProvider};
|
use crate::font::{Font, FontLoader, FontProvider};
|
||||||
use crate::func::Scope;
|
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::parsing::{parse, ParseContext, ParseResult, ParseError};
|
||||||
|
use crate::layout::{layout, LayoutContext, LayoutSpace, LayoutError, LayoutResult, BoxLayout};
|
||||||
|
use crate::style::{PageStyle, TextStyle};
|
||||||
use crate::syntax::SyntaxTree;
|
use crate::syntax::SyntaxTree;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -62,6 +62,8 @@ pub mod font;
|
|||||||
pub mod func;
|
pub mod func;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
pub mod parsing;
|
pub mod parsing;
|
||||||
|
pub mod size;
|
||||||
|
pub mod style;
|
||||||
pub mod syntax;
|
pub mod syntax;
|
||||||
|
|
||||||
|
|
||||||
@ -69,12 +71,12 @@ pub mod syntax;
|
|||||||
///
|
///
|
||||||
/// Can be configured through various methods.
|
/// Can be configured through various methods.
|
||||||
pub struct Typesetter<'p> {
|
pub struct Typesetter<'p> {
|
||||||
/// The default page style.
|
|
||||||
page_style: PageStyle,
|
|
||||||
/// The default text style.
|
|
||||||
text_style: TextStyle,
|
|
||||||
/// Font providers.
|
/// Font providers.
|
||||||
font_providers: Vec<Box<dyn FontProvider + 'p>>,
|
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> {
|
impl<'p> Typesetter<'p> {
|
||||||
@ -82,8 +84,8 @@ impl<'p> Typesetter<'p> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn new() -> Typesetter<'p> {
|
pub fn new() -> Typesetter<'p> {
|
||||||
Typesetter {
|
Typesetter {
|
||||||
page_style: PageStyle::default(),
|
|
||||||
text_style: TextStyle::default(),
|
text_style: TextStyle::default(),
|
||||||
|
page_style: PageStyle::default(),
|
||||||
font_providers: vec![],
|
font_providers: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,36 +117,19 @@ impl<'p> Typesetter<'p> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Layout a syntax tree and return the layout and the referenced font list.
|
/// 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);
|
let loader = FontLoader::new(&self.font_providers);
|
||||||
|
let ctx = LayoutContext {
|
||||||
// Prepare the layouting context.
|
|
||||||
let page = &self.page_style;
|
|
||||||
let mut ctx = LayoutContext {
|
|
||||||
loader: &loader,
|
loader: &loader,
|
||||||
text_style: self.text_style.clone(),
|
style: self.text_style.clone(),
|
||||||
max_extent: Extent {
|
space: LayoutSpace {
|
||||||
width: page.width - page.margin_left - page.margin_right,
|
dimensions: self.page_style.dimensions,
|
||||||
height: page.height - page.margin_top - page.margin_bottom,
|
padding: self.page_style.margins,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Layout the content of the page (without margins).
|
let pages = layout(&tree, &ctx)?;
|
||||||
let content = layout(&tree, &ctx)?;
|
Ok((pages, loader.into_fonts()))
|
||||||
|
|
||||||
// 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()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Typeset a portable document from source code.
|
/// Typeset a portable document from source code.
|
||||||
@ -152,7 +137,7 @@ impl<'p> Typesetter<'p> {
|
|||||||
pub fn typeset(&self, src: &str) -> Result<Document, TypesetError> {
|
pub fn typeset(&self, src: &str) -> Result<Document, TypesetError> {
|
||||||
let tree = self.parse(src)?;
|
let tree = self.parse(src)?;
|
||||||
let (layout, fonts) = self.layout(&tree)?;
|
let (layout, fonts) = self.layout(&tree)?;
|
||||||
let document = layout.into_document(fonts);
|
let document = layout.into_doc(fonts);
|
||||||
Ok(document)
|
Ok(document)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
307
src/size.rs
Normal file
307
src/size.rs
Normal 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
60
src/style.rs
Normal 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),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user