summaryrefslogtreecommitdiff
path: root/src/doc.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-02-12 21:31:35 +0100
committerLaurenz <laurmaedje@gmail.com>2019-02-12 21:31:35 +0100
commit5a600eb354c65ec008cbf020e45705c2f401d669 (patch)
treeb61d6ae3168716ba198c5631934520053c4a57c4 /src/doc.rs
Move crate into workspace subfolder
Diffstat (limited to 'src/doc.rs')
-rw-r--r--src/doc.rs187
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)],
+ });
+ }
+}