diff options
| author | Laurenz <laurmaedje@gmail.com> | 2019-02-12 21:31:35 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2019-02-12 21:31:35 +0100 |
| commit | 5a600eb354c65ec008cbf020e45705c2f401d669 (patch) | |
| tree | b61d6ae3168716ba198c5631934520053c4a57c4 /src/doc.rs | |
Move crate into workspace subfolder
Diffstat (limited to 'src/doc.rs')
| -rw-r--r-- | src/doc.rs | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/src/doc.rs b/src/doc.rs new file mode 100644 index 00000000..04e214a3 --- /dev/null +++ b/src/doc.rs @@ -0,0 +1,187 @@ +//! Generation of abstract documents from syntax trees. + +use std::fmt; +use crate::parsing::{SyntaxTree, Node}; +use crate::font::{Font, BuiltinFont}; + + +/// Abstract representation of a complete typesetted document. +/// +/// This abstract thing can then be serialized into a specific format like PDF. +#[derive(Debug, Clone, PartialEq)] +pub struct Document { + /// The pages of the document. + pub pages: Vec<Page>, + /// The fonts used by the document. + pub fonts: Vec<DocumentFont>, +} + +impl Document { + /// Create a new document without content. + pub fn new() -> Document { + Document { + pages: vec![], + fonts: vec![], + } + } +} + +/// A page of a document. +#[derive(Debug, Clone, PartialEq)] +pub struct Page { + /// The width and height of the page. + pub size: [Size; 2], + /// The contents of the page. + pub contents: Vec<Text>, +} + +/// Plain text. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Text(pub String); + +/// A font (either built-in or external). +#[derive(Debug, Clone, PartialEq)] +pub enum DocumentFont { + /// One of the 14 built-in fonts. + Builtin(BuiltinFont), + /// An externally loaded font. + Loaded(Font), +} + +/// A distance that can be created from different units of length. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Size { + /// The size in typographic points (1/72 inches). + pub points: f32, +} + +impl Size { + /// Create a size from a number of points. + pub fn from_points(points: f32) -> Size { + Size { points } + } + + /// Create a size from a number of inches. + pub fn from_inches(inches: f32) -> Size { + Size { points: inches / 72.0 } + } + + /// Create a size from a number of millimeters. + pub fn from_mm(mm: f32) -> Size { + Size { points: 2.8345 * mm } + } + + /// Create a size from a number of centimeters. + pub fn from_cm(cm: f32) -> Size { + Size { points: 0.028345 * cm } + } +} + + +/// A type that can be generated into a document. +pub trait Generate { + /// Generate a document from self. + fn generate(self) -> GenResult<Document>; +} + +impl Generate for SyntaxTree<'_> { + fn generate(self) -> GenResult<Document> { + Generator::new(self).generate() + } +} + +/// Result type used for parsing. +type GenResult<T> = std::result::Result<T, GenerationError>; + +/// A failure when generating. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct GenerationError { + /// A message describing the error. + pub message: String, +} + +impl fmt::Display for GenerationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "generation error: {}", self.message) + } +} + + +/// Transforms an abstract syntax tree into a document. +#[derive(Debug, Clone)] +struct Generator<'s> { + tree: SyntaxTree<'s>, +} + +impl<'s> Generator<'s> { + /// Create a new generator from a syntax tree. + fn new(tree: SyntaxTree<'s>) -> Generator<'s> { + Generator { tree } + } + + /// Generate the abstract document. + fn generate(&mut self) -> GenResult<Document> { + let fonts = vec![DocumentFont::Builtin(BuiltinFont::Helvetica)]; + + let mut text = String::new(); + for node in &self.tree.nodes { + match node { + Node::Space if !text.is_empty() => text.push(' '), + Node::Space | Node::Newline => (), + Node::Word(word) => text.push_str(word), + + Node::ToggleItalics | Node::ToggleBold | Node::ToggleMath => unimplemented!(), + Node::Func(_) => unimplemented!(), + + } + } + + let page = Page { + size: [Size::from_mm(210.0), Size::from_mm(297.0)], + contents: vec![ Text(text) ], + }; + + Ok(Document { + pages: vec![page], + fonts, + }) + } + + /// Gives a generation error with a message. + #[inline] + fn err<R, S: Into<String>>(&self, message: S) -> GenResult<R> { + Err(GenerationError { message: message.into() }) + } +} + + +#[cfg(test)] +mod generator_tests { + use super::*; + use crate::parsing::{Tokenize, Parse}; + + /// Test if the source gets generated into the document. + fn test(src: &str, doc: Document) { + assert_eq!(src.tokenize().parse().unwrap().generate(), Ok(doc)); + } + + /// Test if generation gives this error for the source code. + fn test_err(src: &str, err: GenerationError) { + assert_eq!(src.tokenize().parse().unwrap().generate(), Err(err)); + } + + #[test] + fn generator_simple() { + test("This is an example of a sentence.", Document { + pages: vec![ + Page { + size: [Size::from_mm(210.0), Size::from_mm(297.0)], + contents: vec![ + Text("This is an example of a sentence.".to_owned()), + ] + } + ], + fonts: vec![DocumentFont::Builtin(BuiltinFont::Helvetica)], + }); + } +} |
