summaryrefslogtreecommitdiff
path: root/src/doc.rs
blob: 04e214a3c4af9726dd11c71ea1ba9b523b4a3917 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
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)],
        });
    }
}