summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-03-11 22:15:34 +0100
committerLaurenz <laurmaedje@gmail.com>2019-03-11 22:15:34 +0100
commit107450ee5ca8e7e0bc03cf6ce9f59268aa20e9f6 (patch)
tree4d9df95eb410d7dab96cb7182d6c981ff27f9273
parent0511979942625e0b1aa77f090621e4f35a2cf242 (diff)
Restructure typeset crate ✈
-rw-r--r--src/doc.rs22
-rw-r--r--src/engine.rs59
-rw-r--r--src/font.rs80
-rw-r--r--src/lib.rs151
-rw-r--r--src/parsing.rs166
-rw-r--r--src/pdf.rs123
-rw-r--r--src/syntax.rs76
-rw-r--r--src/utility.rs5
8 files changed, 361 insertions, 321 deletions
diff --git a/src/doc.rs b/src/doc.rs
index 890da26a..bff4926a 100644
--- a/src/doc.rs
+++ b/src/doc.rs
@@ -4,20 +4,26 @@ use std::ops;
use crate::font::Font;
+/// A representation of a typesetted document.
#[derive(Debug, Clone, PartialEq)]
pub struct Document {
+ /// The pages of the document.
pub pages: Vec<Page>,
+ /// The fonts used in the document.
pub fonts: Vec<Font>,
}
+/// Default styles for a document.
#[derive(Debug, Clone, PartialEq)]
pub struct Style {
- // Paper
+ /// The width and height of the paper.
pub paper_size: [Size; 2],
+ /// The [left, top, right, bottom] margins of the paper.
pub margins: [Size; 4],
- // Font handling
+ /// A fallback list of font families to use.
pub font_families: Vec<String>,
+ /// The default font size.
pub font_size: f32,
}
@@ -37,22 +43,30 @@ impl Default for Style {
}
}
+/// A page with text contents in a document.
#[derive(Debug, Clone, PartialEq)]
pub struct Page {
- pub width: Size,
- pub height: Size,
+ /// The width and height of the page.
+ pub size: [Size; 2],
+ /// Text content on the page.
pub text: Vec<Text>,
}
+/// A series of text command, that can be written on to a page.
#[derive(Debug, Clone, PartialEq)]
pub struct Text {
+ /// The text commands.
pub commands: Vec<TextCommand>,
}
+/// Different commands for rendering text.
#[derive(Debug, Clone, PartialEq)]
pub enum TextCommand {
+ /// Writing of the text.
Text(String),
+ /// Moves from the *start* of the current line by an (x,y) offset.
Move(Size, Size),
+ /// Use the indexed font in the documents font list with a given font size.
SetFont(usize, f32),
}
diff --git a/src/engine.rs b/src/engine.rs
index f7d820e8..34f98766 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -1,53 +1,26 @@
//! Core typesetting engine.
+use std::error;
use std::fmt;
-use crate::parsing::{SyntaxTree, Node};
+use crate::syntax::{SyntaxTree, Node};
use crate::doc::{Document, Style, Page, Text, TextCommand};
use crate::font::Font;
-/// A type that can be typesetted into a document.
-pub trait Typeset {
- /// Generate a document from self.
- fn typeset(self) -> TypeResult<Document>;
-}
-
-impl Typeset for SyntaxTree<'_> {
- fn typeset(self) -> TypeResult<Document> {
- Engine::new(self).typeset()
- }
-}
-
-/// Result type used for parsing.
-type TypeResult<T> = std::result::Result<T, TypesetError>;
-
-/// Errors occuring in the process of typesetting.
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct TypesetError {
- message: String,
-}
-
-impl fmt::Display for TypesetError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- f.write_str(&self.message)
- }
-}
-
-
-/// Transforms an abstract syntax tree into a document.
+/// The core typesetting engine, transforming an abstract syntax tree into a document.
#[derive(Debug, Clone)]
-struct Engine<'s> {
+pub struct Engine<'s> {
tree: SyntaxTree<'s>,
}
impl<'s> Engine<'s> {
/// Create a new generator from a syntax tree.
- fn new(tree: SyntaxTree<'s>) -> Engine<'s> {
+ pub fn new(tree: SyntaxTree<'s>) -> Engine<'s> {
Engine { tree }
}
/// Generate the abstract document.
- fn typeset(&mut self) -> TypeResult<Document> {
+ pub fn typeset(&mut self) -> TypeResult<Document> {
let style = Style::default();
// Load font defined by style
@@ -68,8 +41,7 @@ impl<'s> Engine<'s> {
}
let page = Page {
- width: style.paper_size[0],
- height: style.paper_size[1],
+ size: style.paper_size,
text: vec![Text {
commands: vec![
TextCommand::Move(style.margins[0], style.paper_size[1] - style.margins[1]),
@@ -85,3 +57,20 @@ impl<'s> Engine<'s> {
})
}
}
+
+/// Result type used for parsing.
+type TypeResult<T> = std::result::Result<T, TypesetError>;
+
+/// The error type for typesetting.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct TypesetError {
+ message: String,
+}
+
+impl error::Error for TypesetError {}
+
+impl fmt::Display for TypesetError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str(&self.message)
+ }
+}
diff --git a/src/font.rs b/src/font.rs
index 2f6e2b6a..30542391 100644
--- a/src/font.rs
+++ b/src/font.rs
@@ -1,4 +1,4 @@
-//! Font utility and subsetting.
+//! Font loading, utility and subsetting.
use std::collections::HashMap;
use std::error;
@@ -13,10 +13,15 @@ use crate::doc::Size;
/// An font wrapper which allows to subset a font.
#[derive(Debug, Clone, PartialEq)]
pub struct Font {
+ /// The base name of the font.
pub name: String,
+ /// The raw bytes of the font program.
pub program: Vec<u8>,
+ /// A mapping from character codes to glyph ids.
pub mapping: HashMap<char, u16>,
+ /// The widths of the glyphs indexed by glyph id.
pub widths: Vec<Size>,
+ /// The fallback glyph.
pub default_glyph: u16,
}
@@ -54,7 +59,7 @@ impl Font {
self.mapping.get(&c).map(|&g| g).unwrap_or(self.default_glyph)
}
- /// Encode the given text for our font (into glyph ids).
+ /// Encode the given text for this font (into glyph ids).
#[inline]
pub fn encode(&self, text: &str) -> Vec<u8> {
println!("encoding {} with {:?}", text, self.mapping);
@@ -69,11 +74,9 @@ impl Font {
/// Generate a subsetted version of this font including only the chars listed in
/// `chars`.
///
- /// The resulting pair contains the new font data and the new glyph mapping.
- ///
/// All needed tables will be included (returning an error if a table was not present
/// in the source font) and optional tables will be included if there were present
- /// in the source font.
+ /// in the source font. All other tables will be dropped.
pub fn subsetted<C, I1, S1, I2, S2>(
&self,
chars: C,
@@ -109,6 +112,7 @@ impl Font {
}
}
+#[derive(Debug)]
struct Subsetter<'p> {
// Original font
font: &'p Font,
@@ -128,7 +132,7 @@ struct Subsetter<'p> {
impl<'p> Subsetter<'p> {
fn subset<I1, S1, I2, S2>(mut self, needed_tables: I1, optional_tables: I2)
- -> SubsettingResult<Font>
+ -> SubsetResult<Font>
where
I1: IntoIterator<Item=S1>, S1: AsRef<str>,
I2: IntoIterator<Item=S2>, S2: AsRef<str>
@@ -166,7 +170,7 @@ impl<'p> Subsetter<'p> {
let widths = self.glyphs.iter()
.map(|&glyph| self.font.widths.get(glyph as usize).map(|&w| w)
.take_invalid("missing glyph metrics"))
- .collect::<SubsettingResult<Vec<_>>>()?;
+ .collect::<SubsetResult<Vec<_>>>()?;
let mapping = self.chars.into_iter().enumerate().map(|(i, c)| (c, i as u16))
.collect::<HashMap<char, u16>>();
@@ -180,7 +184,7 @@ impl<'p> Subsetter<'p> {
})
}
- fn build_glyphs(&mut self) -> SubsettingResult<()> {
+ fn build_glyphs(&mut self) -> SubsetResult<()> {
self.read_cmap()?;
let cmap = self.cmap.as_ref().unwrap();
@@ -240,7 +244,7 @@ impl<'p> Subsetter<'p> {
Ok(())
}
- fn write_header(&mut self) -> SubsettingResult<()> {
+ fn write_header(&mut self) -> SubsetResult<()> {
// Create an output buffer
let header_len = 12 + self.records.len() * 16;
let mut header = Vec::with_capacity(header_len);
@@ -282,7 +286,7 @@ impl<'p> Subsetter<'p> {
Ok(())
}
- fn write_table(&mut self, tag: Tag) -> SubsettingResult<()> {
+ fn write_table(&mut self, tag: Tag) -> SubsetResult<()> {
match tag.value() {
b"head" | b"cvt " | b"prep" | b"fpgm" | b"name" | b"post" | b"OS/2" => {
self.copy_table(tag)
@@ -428,15 +432,15 @@ impl<'p> Subsetter<'p> {
}
}
- fn copy_table(&mut self, tag: Tag) -> SubsettingResult<()> {
+ fn copy_table(&mut self, tag: Tag) -> SubsetResult<()> {
self.write_table_body(tag, |this| {
let table = this.get_table_data(tag)?;
Ok(this.body.extend(table))
})
}
- fn write_table_body<F>(&mut self, tag: Tag, writer: F) -> SubsettingResult<()>
- where F: FnOnce(&mut Self) -> SubsettingResult<()> {
+ fn write_table_body<F>(&mut self, tag: Tag, writer: F) -> SubsetResult<()>
+ where F: FnOnce(&mut Self) -> SubsetResult<()> {
let start = self.body.len();
writer(self)?;
let end = self.body.len();
@@ -452,8 +456,7 @@ impl<'p> Subsetter<'p> {
}))
}
- #[inline]
- fn get_table_data(&self, tag: Tag) -> SubsettingResult<&'p [u8]> {
+ fn get_table_data(&self, tag: Tag) -> SubsetResult<&'p [u8]> {
let record = match self.tables.binary_search_by_key(&tag, |r| r.tag) {
Ok(index) => &self.tables[index],
Err(_) => return Err(SubsettingError::MissingTable(tag.to_string())),
@@ -464,26 +467,23 @@ impl<'p> Subsetter<'p> {
.take_bytes()
}
- #[inline]
fn contains(&self, tag: Tag) -> bool {
self.tables.binary_search_by_key(&tag, |r| r.tag).is_ok()
}
- #[inline]
- fn read_cmap(&mut self) -> SubsettingResult<()> {
+ fn read_cmap(&mut self) -> SubsetResult<()> {
Ok(if self.cmap.is_none() {
self.cmap = Some(self.reader.read_table::<CharMap>()?);
})
}
- #[inline]
- fn read_hmtx(&mut self) -> SubsettingResult<()> {
+ fn read_hmtx(&mut self) -> SubsetResult<()> {
Ok(if self.hmtx.is_none() {
self.hmtx = Some(self.reader.read_table::<HorizontalMetrics>()?);
})
}
- fn read_loca(&mut self) -> SubsettingResult<()> {
+ fn read_loca(&mut self) -> SubsetResult<()> {
Ok(if self.loca.is_none() {
let mut table = self.get_table_data("loca".parse().unwrap())?;
let format = self.reader.read_table::<Header>()?.index_to_loc_format;
@@ -505,7 +505,6 @@ impl<'p> Subsetter<'p> {
/// Calculate a checksum over the sliced data as sum of u32's.
/// The data length has to be a multiple of four.
-#[inline]
fn calculate_check_sum(data: &[u8]) -> u32 {
let mut sum = 0u32;
data.chunks_exact(4).for_each(|c| {
@@ -522,26 +521,24 @@ fn calculate_check_sum(data: &[u8]) -> u32 {
trait TakeInvalid<T>: Sized {
/// Pull the type out of the option, returning a subsetting error
/// about an invalid font wrong.
- fn take_invalid<S: Into<String>>(self, message: S) -> SubsettingResult<T>;
+ fn take_invalid<S: Into<String>>(self, message: S) -> SubsetResult<T>;
/// Pull the type out of the option, returning an error about missing
/// bytes if it is nothing.
- #[inline]
- fn take_bytes(self) -> SubsettingResult<T> {
+ fn take_bytes(self) -> SubsetResult<T> {
self.take_invalid("expected more bytes")
}
}
impl<T> TakeInvalid<T> for Option<T> {
- #[inline]
- fn take_invalid<S: Into<String>>(self, message: S) -> SubsettingResult<T> {
+ fn take_invalid<S: Into<String>>(self, message: S) -> SubsetResult<T> {
self.ok_or(SubsettingError::Opentype(opentype::Error::InvalidFont(message.into())))
}
}
-type SubsettingResult<T> = Result<T, SubsettingError>;
+type SubsetResult<T> = Result<T, SubsettingError>;
-/// A failure when subsetting a font.
+/// The error type for font subsetting.
#[derive(Debug)]
pub enum SubsettingError {
/// A requested table was not present in the source font.
@@ -564,6 +561,19 @@ impl error::Error for SubsettingError {
}
}
+impl fmt::Display for SubsettingError {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use SubsettingError::*;
+ match self {
+ MissingTable(table) => write!(f, "missing table: {}", table),
+ UnsupportedTable(table) => write!(f, "unsupported table: {}", table),
+ MissingCharacter(c) => write!(f, "missing character: {}", c),
+ Opentype(err) => fmt::Display::fmt(err, f),
+ }
+ }
+}
+
impl From<io::Error> for SubsettingError {
#[inline]
fn from(err: io::Error) -> SubsettingError {
@@ -580,15 +590,3 @@ impl From<opentype::Error> for SubsettingError {
}
}
}
-
-impl fmt::Display for SubsettingError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use SubsettingError::*;
- match self {
- MissingTable(table) => write!(f, "missing table: {}", table),
- UnsupportedTable(table) => write!(f, "unsupported table: {}", table),
- MissingCharacter(c) => write!(f, "missing character: {}", c),
- Opentype(err) => fmt::Display::fmt(err, f),
- }
- }
-}
diff --git a/src/lib.rs b/src/lib.rs
index f2c9b5eb..e9d0b1b4 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,7 +5,7 @@
//! # Example
//! This is an example of compiling a really simple document into _PDF_.
//! ```
-//! use typeset::{parsing::ParseTree, engine::Typeset, write::WritePdf};
+//! use typeset::Compiler;
//!
//! // Create an output file.
//! # /*
@@ -13,22 +13,147 @@
//! # */
//! # let mut file = std::fs::File::create("../target/typeset-hello.pdf").unwrap();
//!
-//! // Parse the source and then generate the document.
-//! let src = "Hello World from Typeset‼";
-//! let doc = src.parse_tree().unwrap().typeset().unwrap();
+//! // Create a compiler and export a PDF.
+//! let src = "Hello World from Typeset!";
+//! let compiler = Compiler::new(src);
//!
-//! // Write the document into file as PDF.
-//! file.write_pdf(&doc).unwrap();
+//! // Write the document into a file as a PDF.
+//! compiler.write_pdf(&mut file).unwrap();
//! ```
-mod pdf;
-mod utility;
-pub mod parsing;
+pub mod syntax;
pub mod doc;
-pub mod engine;
pub mod font;
+mod parsing;
+mod engine;
+mod pdf;
+mod utility;
+
+pub use crate::parsing::{Tokens, Parser, ParseError};
+pub use crate::engine::{Engine, TypesetError};
+pub use crate::pdf::{PdfCreator, PdfWritingError};
+
+use std::error;
+use std::fmt;
+use std::io::Write;
+use crate::syntax::SyntaxTree;
+use crate::doc::Document;
+
+
+/// Emits various compiled intermediates from source code.
+pub struct Compiler<'s> {
+ /// The source code of the document.
+ source: &'s str,
+}
+
+impl<'s> Compiler<'s> {
+ /// Create a new compiler from a document.
+ #[inline]
+ pub fn new(source: &'s str) -> Compiler<'s> {
+ Compiler { source }
+ }
+
+ /// Return an iterator over the tokens of the document.
+ #[inline]
+ pub fn tokenize(&self) -> Tokens<'s> {
+ Tokens::new(self.source)
+ }
+
+ /// Return the abstract syntax tree representation of the document.
+ #[inline]
+ pub fn parse(&self) -> Result<SyntaxTree<'s>, Error> {
+ Parser::new(self.tokenize()).parse().map_err(Into::into)
+ }
+
+ /// Return the abstract typesetted representation of the document.
+ #[inline]
+ pub fn typeset(&self) -> Result<Document, Error> {
+ Engine::new(self.parse()?).typeset().map_err(Into::into)
+ }
+
+ /// Write the document as a _PDF_, returning how many bytes were written.
+ pub fn write_pdf<W: Write>(&self, target: &mut W) -> Result<usize, Error> {
+ PdfCreator::new(target, &self.typeset()?)?.write().map_err(Into::into)
+ }
+}
+
+/// The error type for compilation.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum Error {
+ /// An error that occured while transforming source code into
+ /// an abstract syntax tree.
+ Parse(ParseError),
+ /// An error that occured while typesetting into an abstract document.
+ Typeset(TypesetError),
+ /// An error that occured while writing the document as a _PDF_.
+ PdfWrite(PdfWritingError)
+}
+
+impl error::Error for Error {
+ #[inline]
+ fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+ match self {
+ Error::Parse(err) => Some(err),
+ Error::Typeset(err) => Some(err),
+ Error::PdfWrite(err) => Some(err),
+ }
+ }
+}
+
+impl fmt::Display for Error {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Error::Parse(err) => write!(f, "parse error: {}", err),
+ Error::Typeset(err) => write!(f, "typeset error: {}", err),
+ Error::PdfWrite(err) => write!(f, "typeset error: {}", err),
+ }
+ }
+}
+
+impl From<ParseError> for Error {
+ #[inline]
+ fn from(err: ParseError) -> Error {
+ Error::Parse(err)
+ }
+}
+
+impl From<TypesetError> for Error {
+ #[inline]
+ fn from(err: TypesetError) -> Error {
+ Error::Typeset(err)
+ }
+}
+
+impl From<PdfWritingError> for Error {
+ #[inline]
+ fn from(err: PdfWritingError) -> Error {
+ Error::PdfWrite(err)
+ }
+}
+
+
+#[cfg(test)]
+mod test {
+ use crate::Compiler;
+
+ /// Create a pdf with a name from the source code.
+ fn test(name: &str, src: &str) {
+ let path = format!("../target/typeset-pdf-{}.pdf", name);
+ let mut file = std::fs::File::create(path).unwrap();
+ Compiler::new(src).write_pdf(&mut file).unwrap();
+ }
-/// Writing of documents into supported formats.
-pub mod write {
- pub use crate::pdf::{WritePdf, PdfWritingError};
+ #[test]
+ fn pdf() {
+ test("unicode", "∑mbe∂∂ed font with Unicode!");
+ test("parentheses", "Text with ) and ( or (enclosed) works.");
+ test("composite-glyph", "Composite character‼");
+ test("multiline","
+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
+ diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed
+ diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
+ Stet clita kasd gubergren, no sea takimata sanctus est.
+ ");
+ }
}
diff --git a/src/parsing.rs b/src/parsing.rs
index ece65edd..37c83b3f 100644
--- a/src/parsing.rs
+++ b/src/parsing.rs
@@ -1,56 +1,14 @@
//! Parsing of source code into tokens and syntax trees.
+use std::error;
use std::fmt;
use std::iter::Peekable;
use std::mem::swap;
use unicode_segmentation::{UnicodeSegmentation, UWordBounds};
+use crate::syntax::*;
use crate::utility::{Splinor, Spline, Splined, StrExt};
-/// A logical unit of the incoming text stream.
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub enum Token<'s> {
- /// One or more whitespace (non-newline) codepoints.
- Space,
- /// A line feed (either `\n` or `\r\n`).
- Newline,
- /// A left bracket: `[`.
- LeftBracket,
- /// A right bracket: `]`.
- RightBracket,
- /// A colon (`:`) indicating the beginning of function arguments.
- ///
- /// If a colon occurs outside of the function header, it will be
- /// tokenized as a [Word](Token::Word).
- Colon,
- /// Same as with [Colon](Token::Colon).
- Equals,
- /// Two underscores, indicating text in _italics_.
- DoubleUnderscore,
- /// Two stars, indicating **bold** text.
- DoubleStar,
- /// A dollar sign, indicating _mathematical_ content.
- Dollar,
- /// A hashtag starting a _comment_.
- Hashtag,
- /// Everything else just is a literal word.
- Word(&'s str),
-}
-
-
-/// A type that is separable into logical units (tokens).
-pub trait Tokenize {
- /// Tokenize self into logical units.
- fn tokenize<'s>(&'s self) -> Tokens<'s>;
-}
-
-impl Tokenize for str {
- fn tokenize<'s>(&'s self) -> Tokens<'s> {
- Tokens::new(self)
- }
-}
-
-
/// An iterator over the tokens of a text.
#[derive(Clone)]
pub struct Tokens<'s> {
@@ -253,99 +211,9 @@ impl<'s> Tokens<'s> {
}
}
-
-/// A tree representation of the source.
-#[derive(Debug, Clone, PartialEq)]
-pub struct SyntaxTree<'s> {
- /// The children.
- pub nodes: Vec<Node<'s>>,
-}
-
-impl<'s> SyntaxTree<'s> {
- /// Create an empty syntax tree.
- #[inline]
- pub fn new() -> SyntaxTree<'s> {
- SyntaxTree { nodes: vec![] }
- }
-}
-
-/// A node in the abstract syntax tree.
-#[derive(Debug, Clone, PartialEq)]
-pub enum Node<'s> {
- /// Whitespace between other nodes.
- Space,
- /// A line feed.
- Newline,
- /// Indicates that italics were enabled/disabled.
- ToggleItalics,
- /// Indicates that boldface was enabled/disabled.
- ToggleBold,
- /// Indicates that math mode was enabled/disabled.
- ToggleMath,
- /// A literal word.
- Word(&'s str),
- /// A function invocation.
- Func(Function<'s>),
-}
-
-/// A node representing a function invocation.
-#[derive(Debug, Clone, PartialEq)]
-pub struct Function<'s> {
- /// The name of the function.
- pub name: &'s str,
- /// Some syntax tree if the function had a body (second set of brackets),
- /// otherwise nothing.
- pub body: Option<SyntaxTree<'s>>,
-}
-
-
-/// A type that is parsable into a syntax tree.
-pub trait ParseTree<'s> {
- /// Parse self into a syntax tree.
- fn parse_tree(self) -> ParseResult<SyntaxTree<'s>>;
-}
-
-impl<'s> ParseTree<'s> for &'s str {
- #[inline]
- fn parse_tree(self) -> ParseResult<SyntaxTree<'s>> {
- self.tokenize().parse_tree()
- }
-}
-
-impl<'s> ParseTree<'s> for Tokens<'s> {
- #[inline]
- fn parse_tree(self) -> ParseResult<SyntaxTree<'s>> {
- Parser::new(self).parse()
- }
-}
-
-impl<'s> ParseTree<'s> for Vec<Token<'s>> {
- #[inline]
- fn parse_tree(self) -> ParseResult<SyntaxTree<'s>> {
- Parser::new(self.into_iter()).parse()
- }
-}
-
-/// Result type used for parsing.
-type ParseResult<T> = std::result::Result<T, ParseError>;
-
-/// A failure when parsing.
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct ParseError {
- /// A message describing the error.
- message: String,
-}
-
-impl fmt::Display for ParseError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- f.write_str(&self.message)
- }
-}
-
-
/// Parses a token stream into an abstract syntax tree.
#[derive(Debug, Clone)]
-struct Parser<'s, T> where T: Iterator<Item = Token<'s>> {
+pub struct Parser<'s, T> where T: Iterator<Item = Token<'s>> {
tokens: Peekable<T>,
state: ParserState,
stack: Vec<Function<'s>>,
@@ -363,7 +231,7 @@ enum ParserState {
impl<'s, T> Parser<'s, T> where T: Iterator<Item = Token<'s>> {
/// Create a new parser from a type that emits results of tokens.
- fn new(tokens: T) -> Parser<'s, T> {
+ pub fn new(tokens: T) -> Parser<'s, T> {
Parser {
tokens: tokens.peekable(),
state: ParserState::Body,
@@ -373,7 +241,7 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item = Token<'s>> {
}
/// Parse into an abstract syntax tree.
- fn parse(mut self) -> ParseResult<SyntaxTree<'s>> {
+ pub fn parse(mut self) -> ParseResult<SyntaxTree<'s>> {
use ParserState as PS;
while let Some(token) = self.tokens.next() {
@@ -487,6 +355,24 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item = Token<'s>> {
}
}
+/// Result type used for parsing.
+type ParseResult<T> = std::result::Result<T, ParseError>;
+
+/// The error type for parsing.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct ParseError {
+ /// A message describing the error.
+ message: String,
+}
+
+impl error::Error for ParseError {}
+
+impl fmt::Display for ParseError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str(&self.message)
+ }
+}
+
#[cfg(test)]
mod token_tests {
@@ -497,7 +383,7 @@ mod token_tests {
/// Test if the source code tokenizes to the tokens.
fn test(src: &str, tokens: Vec<Token>) {
- assert_eq!(src.tokenize().collect::<Vec<_>>(), tokens);
+ assert_eq!(Tokens::new(src).collect::<Vec<_>>(), tokens);
}
/// Tokenizes the basic building blocks.
@@ -605,12 +491,12 @@ mod parse_tests {
/// Test if the source code parses into the syntax tree.
fn test(src: &str, tree: SyntaxTree) {
- assert_eq!(src.parse_tree(), Ok(tree));
+ assert_eq!(Parser::new(Tokens::new(src)).parse(), Ok(tree));
}
/// Test if the source parses into the error.
fn test_err(src: &str, err: ParseError) {
- assert_eq!(src.parse_tree(), Err(err));
+ assert_eq!(Parser::new(Tokens::new(src)).parse(), Err(err));
}
/// Short cut macro to create a syntax tree.
diff --git a/src/pdf.rs b/src/pdf.rs
index b23a1343..d5d3b081 100644
--- a/src/pdf.rs
+++ b/src/pdf.rs
@@ -1,6 +1,7 @@
//! Writing of documents in the _PDF_ format.
use std::collections::HashSet;
+use std::error;
use std::fmt;
use std::io::{self, Write, Cursor};
use pdf::{PdfWriter, Reference, Rect, Version, Trailer};
@@ -12,61 +13,8 @@ use crate::doc::{self, Document, TextCommand};
use crate::font::Font;
-/// A type that is a sink for documents that can be written in the _PDF_ format.
-pub trait WritePdf {
- /// Write a document into self, returning how many bytes were written.
- fn write_pdf(&mut self, doc: &Document) -> PdfResult<usize>;
-}
-
-impl<W: Write> WritePdf for W {
- #[inline]
- fn write_pdf(&mut self, doc: &Document) -> PdfResult<usize> {
- PdfCreator::new(self, doc)?.write()
- }
-}
-
-/// Result type used for parsing.
-type PdfResult<T> = std::result::Result<T, PdfWritingError>;
-
-/// A failure while writing a _PDF_.
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct PdfWritingError {
- /// A message describing the error.
- message: String,
-}
-
-impl From<io::Error> for PdfWritingError {
- #[inline]
- fn from(err: io::Error) -> PdfWritingError {
- PdfWritingError { message: format!("{}", err) }
- }
-}
-
-impl From<opentype::Error> for PdfWritingError {
- #[inline]
- fn from(err: opentype::Error) -> PdfWritingError {
- PdfWritingError { message: format!("{}", err) }
- }
-}
-
-impl From<crate::font::SubsettingError> for PdfWritingError {
- #[inline]
- fn from(err: crate::font::SubsettingError) -> PdfWritingError {
- PdfWritingError { message: format!("{}", err) }
- }
-}
-
-impl fmt::Display for PdfWritingError {
- #[inline]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- f.write_str(&self.message)
- }
-}
-
-
-/// Keeps track of the document while letting the pdf writer
-/// generate the _PDF_.
-struct PdfCreator<'a, W: Write> {
+/// Writes documents in the _PDF_ format.
+pub struct PdfCreator<'a, W: Write> {
writer: PdfWriter<'a, W>,
doc: &'a Document,
offsets: Offsets,
@@ -137,7 +85,7 @@ impl<'a, W: Write> PdfCreator<'a, W> {
}
/// Write the complete document.
- fn write(&mut self) -> PdfResult<usize> {
+ pub fn write(&mut self) -> PdfResult<usize> {
// Header
self.writer.write_header(&Version::new(1, 7))?;
@@ -174,8 +122,8 @@ impl<'a, W: Write> PdfCreator<'a, W> {
// The page objects
let mut id = self.offsets.pages.0;
for page in &self.doc.pages {
- let width = page.width.to_points();
- let height = page.height.to_points();
+ let width = page.size[0].to_points();
+ let height = page.size[1].to_points();
self.writer.write_obj(id, Page::new(self.offsets.page_tree)
.media_box(Rect::new(0.0, 0.0, width, height))
@@ -274,7 +222,6 @@ impl<'a, W: Write> PdfCreator<'a, W> {
}
}
-
/// The data we need from the font.
struct PdfFont {
font: Font,
@@ -302,7 +249,7 @@ impl PdfFont {
let subsetted = font.subsetted(
chars.iter().cloned(),
&["head", "hhea", "maxp", "hmtx", "loca", "glyf"],
- &["cvt ", "prep", "fpgm", "OS/2", "cmap", "name", "post"],
+ &["cvt ", "prep", "fpgm", /* "OS/2", "cmap", "name", "post" */],
)?;
let mut flags = FontFlags::empty();
@@ -341,37 +288,47 @@ impl PdfFont {
impl std::ops::Deref for PdfFont {
type Target = Font;
- #[inline]
fn deref(&self) -> &Font {
&self.font
}
}
+/// Result type used for parsing.
+type PdfResult<T> = std::result::Result<T, PdfWritingError>;
+
+/// The error type for _PDF_ creation.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct PdfWritingError {
+ /// A message describing the error.
+ message: String,
+}
+
+impl error::Error for PdfWritingError {}
+
+impl From<io::Error> for PdfWritingError {
+ #[inline]
+ fn from(err: io::Error) -> PdfWritingError {
+ PdfWritingError { message: format!("{}", err) }
+ }
+}
-#[cfg(test)]
-mod pdf_tests {
- use super::*;
- use crate::parsing::ParseTree;
- use crate::engine::Typeset;
+impl From<opentype::Error> for PdfWritingError {
+ #[inline]
+ fn from(err: opentype::Error) -> PdfWritingError {
+ PdfWritingError { message: format!("{}", err) }
+ }
+}
- /// Create a pdf with a name from the source code.
- fn test(name: &str, src: &str) {
- let doc = src.parse_tree().unwrap().typeset().unwrap();
- let path = format!("../target/typeset-pdf-{}.pdf", name);
- let mut file = std::fs::File::create(path).unwrap();
- file.write_pdf(&doc).unwrap();
+impl From<crate::font::SubsettingError> for PdfWritingError {
+ #[inline]
+ fn from(err: crate::font::SubsettingError) -> PdfWritingError {
+ PdfWritingError { message: format!("{}", err) }
}
+}
- #[test]
- fn pdf() {
- test("unicode", "∑mbe∂∂ed font with Unicode!");
- test("parentheses", "Text with ) and ( or (enclosed) works.");
- test("composite-glyph", "Composite character‼");
- test("multiline","
- Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
- diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed
- diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
- Stet clita kasd gubergren, no sea takimata sanctus est.
- ");
+impl fmt::Display for PdfWritingError {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str(&self.message)
}
}
diff --git a/src/syntax.rs b/src/syntax.rs
new file mode 100644
index 00000000..38e5a60e
--- /dev/null
+++ b/src/syntax.rs
@@ -0,0 +1,76 @@
+//! Token and abstract syntax tree representations.
+
+
+/// A logical unit of the incoming text stream.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum Token<'s> {
+ /// One or more whitespace (non-newline) codepoints.
+ Space,
+ /// A line feed (either `\n` or `\r\n`).
+ Newline,
+ /// A left bracket: `[`.
+ LeftBracket,
+ /// A right bracket: `]`.
+ RightBracket,
+ /// A colon (`:`) indicating the beginning of function arguments.
+ ///
+ /// If a colon occurs outside of the function header, it will be
+ /// tokenized as a [Word](Token::Word).
+ Colon,
+ /// Same as with [Colon](Token::Colon).
+ Equals,
+ /// Two underscores, indicating text in _italics_.
+ DoubleUnderscore,
+ /// Two stars, indicating **bold** text.
+ DoubleStar,
+ /// A dollar sign, indicating _mathematical_ content.
+ Dollar,
+ /// A hashtag starting a _comment_.
+ Hashtag,
+ /// Everything else just is a literal word.
+ Word(&'s str),
+}
+
+/// A tree representation of the source.
+#[derive(Debug, Clone, PartialEq)]
+pub struct SyntaxTree<'s> {
+ /// The children.
+ pub nodes: Vec<Node<'s>>,
+}
+
+impl<'s> SyntaxTree<'s> {
+ /// Create an empty syntax tree.
+ #[inline]
+ pub fn new() -> SyntaxTree<'s> {
+ SyntaxTree { nodes: vec![] }
+ }
+}
+
+/// A node in the abstract syntax tree.
+#[derive(Debug, Clone, PartialEq)]
+pub enum Node<'s> {
+ /// Whitespace between other nodes.
+ Space,
+ /// A line feed.
+ Newline,
+ /// Indicates that italics were enabled/disabled.
+ ToggleItalics,
+ /// Indicates that boldface was enabled/disabled.
+ ToggleBold,
+ /// Indicates that math mode was enabled/disabled.
+ ToggleMath,
+ /// A literal word.
+ Word(&'s str),
+ /// A function invocation.
+ Func(Function<'s>),
+}
+
+/// A node representing a function invocation.
+#[derive(Debug, Clone, PartialEq)]
+pub struct Function<'s> {
+ /// The name of the function.
+ pub name: &'s str,
+ /// Some syntax tree if the function had a body (second set of brackets),
+ /// otherwise nothing.
+ pub body: Option<SyntaxTree<'s>>,
+}
diff --git a/src/utility.rs b/src/utility.rs
index 4e6bc7d9..efe519c4 100644
--- a/src/utility.rs
+++ b/src/utility.rs
@@ -29,7 +29,6 @@ pub trait Splinor {
}
impl Splinor for str {
- #[inline]
fn spline<'s, T: Clone>(&'s self, pat: &'s str, splinor: T) -> Spline<'s, T> {
Spline {
splinor: Splined::Splinor(splinor),
@@ -61,7 +60,6 @@ pub enum Splined<'s, T> {
impl<'s, T: Clone> Iterator for Spline<'s, T> {
type Item = Splined<'s, T>;
- #[inline]
fn next(&mut self) -> Option<Splined<'s, T>> {
if self.next_splinor && self.split.peek().is_some() {
self.next_splinor = false;
@@ -73,7 +71,6 @@ impl<'s, T: Clone> Iterator for Spline<'s, T> {
}
}
-
/// More useful functions on `str`'s.
pub trait StrExt {
/// Whether self consists only of whitespace.
@@ -84,12 +81,10 @@ pub trait StrExt {
}
impl StrExt for str {
- #[inline]
fn is_whitespace(&self) -> bool {
self.chars().all(|c| c.is_whitespace() && c != '\n')
}
- #[inline]
fn is_identifier(&self) -> bool {
let mut chars = self.chars();