summaryrefslogtreecommitdiff
path: root/src/engine
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-03-30 16:58:45 +0100
committerLaurenz <laurmaedje@gmail.com>2019-03-30 16:58:45 +0100
commitdb96ecae94a7c06d04528e8d4461ebca86d2d249 (patch)
tree9f1bdbaa65b424ef522dadda02bfa4826acfc836 /src/engine
parentf683bba4004cc07f9ac91d5d99a6bab76f335dba (diff)
Move some types into better places 🧱
Diffstat (limited to 'src/engine')
-rw-r--r--src/engine/mod.rs212
-rw-r--r--src/engine/size.rs144
2 files changed, 356 insertions, 0 deletions
diff --git a/src/engine/mod.rs b/src/engine/mod.rs
new file mode 100644
index 00000000..f121ac82
--- /dev/null
+++ b/src/engine/mod.rs
@@ -0,0 +1,212 @@
+//! Core typesetting engine.
+
+use crate::syntax::{SyntaxTree, Node};
+use crate::doc::{Document, Page, Text, TextCommand};
+use crate::font::{Font, FontFamily, FontConfig, FontError};
+use crate::Context;
+
+mod size;
+pub use size::Size;
+
+
+/// The core typesetting engine, transforming an abstract syntax tree into a document.
+pub(crate) struct Engine<'a> {
+ // Immutable
+ tree: &'a SyntaxTree<'a>,
+ ctx: &'a Context<'a>,
+
+ // Mutable
+ fonts: Vec<Font>,
+ active_font: usize,
+ text_commands: Vec<TextCommand>,
+ current_line: String,
+ current_width: Size,
+}
+
+impl<'a> Engine<'a> {
+ /// Create a new generator from a syntax tree.
+ pub fn new(tree: &'a SyntaxTree<'a>, context: &'a Context<'a>) -> Engine<'a> {
+ Engine {
+ tree,
+ ctx: context,
+ fonts: Vec::new(),
+ active_font: 0,
+ text_commands: Vec::new(),
+ current_line: String::new(),
+ current_width: Size::zero(),
+ }
+ }
+
+ /// Generate the abstract document.
+ pub fn typeset(mut self) -> TypeResult<Document> {
+ // Load font defined by style
+ let mut font = None;
+ let config = FontConfig::new(self.ctx.style.font_families.clone());
+ for provider in &self.ctx.font_providers {
+ if let Some(mut source) = provider.provide(&config) {
+ let mut program = Vec::new();
+ source.read_to_end(&mut program)?;
+ font = Some(Font::new(program)?);
+ break;
+ }
+ }
+
+ let font = match font {
+ Some(font) => font,
+ None => return Err(TypesetError::MissingFont),
+ };
+
+ self.fonts.push(font);
+ self.active_font = 0;
+
+ // Move cursor to top-left position
+ self.text_commands.push(TextCommand::Move(
+ self.ctx.style.margin_left,
+ self.ctx.style.height - self.ctx.style.margin_top
+ ));
+
+ // Set the current font
+ self.text_commands.push(TextCommand::SetFont(0, self.ctx.style.font_size));
+
+ // Iterate through the documents nodes.
+ for node in &self.tree.nodes {
+ match node {
+ Node::Word(word) => self.write_word(word),
+
+ Node::Space => self.write_space(),
+ Node::Newline => (),
+
+ Node::ToggleItalics | Node::ToggleBold | Node::ToggleMath => unimplemented!(),
+ Node::Func(_) => unimplemented!(),
+ }
+ }
+
+ // Create a page from the contents.
+ let page = Page {
+ width: self.ctx.style.width,
+ height: self.ctx.style.height,
+ text: vec![Text {
+ commands: self.text_commands,
+ }],
+ };
+
+ Ok(Document {
+ pages: vec![page],
+ fonts: self.fonts,
+ })
+ }
+
+ fn write_word(&mut self, word: &str) {
+ let font = &self.fonts[self.active_font];
+
+ let width = self.width(word);
+ if self.would_overflow(width) {
+ let vertical_move = - self.ctx.style.font_size
+ * self.ctx.style.line_spacing
+ * font.metrics.ascender;
+ self.text_commands.push(TextCommand::Move(Size::zero(), vertical_move));
+
+ self.current_line.clear();
+ self.current_width = Size::zero();
+ }
+
+ self.text_commands.push(TextCommand::Text(word.to_owned()));
+ self.current_line.push_str(word);
+ self.current_width += width;
+ }
+
+ fn write_space(&mut self) {
+ let space_width = self.width(" ");
+
+ if !self.would_overflow(space_width) && !self.current_line.is_empty() {
+ self.text_commands.push(TextCommand::Text(" ".to_owned()));
+ self.current_line.push_str(" ");
+ self.current_width += space_width;
+ }
+ }
+
+ fn width(&self, word: &str) -> Size {
+ let font = &self.fonts[self.active_font];
+ word.chars()
+ .map(|c| font.widths[font.map(c) as usize] * self.ctx.style.font_size)
+ .sum()
+ }
+
+ fn would_overflow(&self, width: Size) -> bool {
+ let max_width = self.ctx.style.width
+ - self.ctx.style.margin_left
+ - self.ctx.style.margin_right;
+
+ self.current_width + width > max_width
+ }
+}
+
+/// Default styles for a document.
+#[derive(Debug, Clone, PartialEq)]
+pub struct Style {
+ /// The width of the paper.
+ pub width: Size,
+ /// The height of the paper.
+ pub height: Size,
+
+ /// The left margin of the paper.
+ pub margin_left: Size,
+ /// The top margin of the paper.
+ pub margin_top: Size,
+ /// The right margin of the paper.
+ pub margin_right: Size,
+ /// The bottom margin of the paper.
+ pub margin_bottom: Size,
+
+ /// 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,
+}
+
+impl Default for Style {
+ fn default() -> Style {
+ use FontFamily::*;
+ Style {
+ // A4 paper.
+ width: Size::from_mm(210.0),
+ height: Size::from_mm(297.0),
+
+ // Margins. A bit more on top and bottom.
+ margin_left: Size::from_cm(2.5),
+ margin_top: Size::from_cm(3.0),
+ margin_right: Size::from_cm(2.5),
+ margin_bottom: Size::from_cm(3.0),
+
+ // Default font family, font size and line spacing.
+ font_families: vec![SansSerif, Serif, Monospace],
+ font_size: 12.0,
+ line_spacing: 1.25,
+ }
+ }
+}
+
+/// The error type for typesetting.
+pub enum TypesetError {
+ /// There was no suitable font.
+ MissingFont,
+ /// An error occured while gathering font data.
+ Font(FontError),
+}
+
+error_type! {
+ err: TypesetError,
+ res: TypeResult,
+ show: f => match err {
+ TypesetError::MissingFont => write!(f, "missing font"),
+ TypesetError::Font(err) => write!(f, "font error: {}", err),
+ },
+ source: match err {
+ TypesetError::Font(err) => Some(err),
+ _ => None,
+ },
+ from: (std::io::Error, TypesetError::Font(FontError::Io(err))),
+ from: (FontError, TypesetError::Font(err)),
+}
diff --git a/src/engine/size.rs b/src/engine/size.rs
new file mode 100644
index 00000000..a6624c57
--- /dev/null
+++ b/src/engine/size.rs
@@ -0,0 +1,144 @@
+use std::cmp::Ordering;
+use std::fmt::{self, Display, Debug, Formatter};
+use std::iter::Sum;
+use std::ops::*;
+
+
+/// A general distance type that can convert between units.
+#[derive(Copy, Clone, PartialEq)]
+pub struct Size {
+ /// The size in typographic points (1/72 inches).
+ points: f32,
+}
+
+impl Size {
+ /// Create an zeroed size.
+ #[inline]
+ pub fn zero() -> Size { Size { points: 0.0 } }
+
+ /// Create a size from a number of points.
+ #[inline]
+ pub fn from_points(points: f32) -> Size { Size { points } }
+
+ /// Create a size from a number of inches.
+ #[inline]
+ pub fn from_inches(inches: f32) -> Size { Size { points: 72.0 * inches } }
+
+ /// Create a size from a number of millimeters.
+ #[inline]
+ pub fn from_mm(mm: f32) -> Size { Size { points: 2.83465 * mm } }
+
+ /// Create a size from a number of centimeters.
+ #[inline]
+ pub fn from_cm(cm: f32) -> Size { Size { points: 28.3465 * cm } }
+
+ /// Create a size from a number of points.
+ #[inline]
+ pub fn to_points(&self) -> f32 { self.points }
+
+ /// Create a size from a number of inches.
+ #[inline]
+ pub fn to_inches(&self) -> f32 { self.points * 0.0138889 }
+
+ /// Create a size from a number of millimeters.
+ #[inline]
+ pub fn to_mm(&self) -> f32 { self.points * 0.352778 }
+
+ /// Create a size from a number of 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 {
+ fn partial_cmp(&self, other: &Size) -> Option<Ordering> {
+ self.points.partial_cmp(&other.points)
+ }
+}
+
+impl Neg for Size {
+ type Output = Size;
+
+ fn neg(self) -> Size {
+ Size { points: -self.points }
+ }
+}
+
+impl Sum for Size {
+ 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);