diff --git a/src/doc.rs b/src/doc.rs index 890da26a2..bff4926ab 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, + /// The fonts used in the document. pub fonts: Vec, } +/// 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, + /// 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, } +/// 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, } +/// 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 f7d820e88..34f987663 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; -} - -impl Typeset for SyntaxTree<'_> { - fn typeset(self) -> TypeResult { - Engine::new(self).typeset() - } -} - -/// Result type used for parsing. -type TypeResult = std::result::Result; - -/// 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 { + pub fn typeset(&mut self) -> TypeResult { 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 = std::result::Result; + +/// 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 2f6e2b6a9..305423917 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, + /// A mapping from character codes to glyph ids. pub mapping: HashMap, + /// The widths of the glyphs indexed by glyph id. pub widths: Vec, + /// 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 { 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( &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(mut self, needed_tables: I1, optional_tables: I2) - -> SubsettingResult + -> SubsetResult where I1: IntoIterator, S1: AsRef, I2: IntoIterator, S2: AsRef @@ -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::>>()?; + .collect::>>()?; let mapping = self.chars.into_iter().enumerate().map(|(i, c)| (c, i as u16)) .collect::>(); @@ -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(&mut self, tag: Tag, writer: F) -> SubsettingResult<()> - where F: FnOnce(&mut Self) -> SubsettingResult<()> { + fn write_table_body(&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::()?); }) } - #[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::()?); }) } - 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::
()?.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: Sized { /// Pull the type out of the option, returning a subsetting error /// about an invalid font wrong. - fn take_invalid>(self, message: S) -> SubsettingResult; + fn take_invalid>(self, message: S) -> SubsetResult; /// Pull the type out of the option, returning an error about missing /// bytes if it is nothing. - #[inline] - fn take_bytes(self) -> SubsettingResult { + fn take_bytes(self) -> SubsetResult { self.take_invalid("expected more bytes") } } impl TakeInvalid for Option { - #[inline] - fn take_invalid>(self, message: S) -> SubsettingResult { + fn take_invalid>(self, message: S) -> SubsetResult { self.ok_or(SubsettingError::Opentype(opentype::Error::InvalidFont(message.into()))) } } -type SubsettingResult = Result; +type SubsetResult = Result; -/// 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 for SubsettingError { #[inline] fn from(err: io::Error) -> SubsettingError { @@ -580,15 +590,3 @@ impl From 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 f2c9b5ebe..e9d0b1b4d 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(); //! ``` +pub mod syntax; +pub mod doc; +pub mod font; +mod parsing; +mod engine; mod pdf; mod utility; -pub mod parsing; -pub mod doc; -pub mod engine; -pub mod font; -/// Writing of documents into supported formats. -pub mod write { - pub use crate::pdf::{WritePdf, PdfWritingError}; +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, Error> { + Parser::new(self.tokenize()).parse().map_err(Into::into) + } + + /// Return the abstract typesetted representation of the document. + #[inline] + pub fn typeset(&self) -> Result { + 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(&self, target: &mut W) -> Result { + 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 for Error { + #[inline] + fn from(err: ParseError) -> Error { + Error::Parse(err) + } +} + +impl From for Error { + #[inline] + fn from(err: TypesetError) -> Error { + Error::Typeset(err) + } +} + +impl From 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(); + } + + #[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 ece65edd6..37c83b3ff 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>, -} - -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>, -} - - -/// A type that is parsable into a syntax tree. -pub trait ParseTree<'s> { - /// Parse self into a syntax tree. - fn parse_tree(self) -> ParseResult>; -} - -impl<'s> ParseTree<'s> for &'s str { - #[inline] - fn parse_tree(self) -> ParseResult> { - self.tokenize().parse_tree() - } -} - -impl<'s> ParseTree<'s> for Tokens<'s> { - #[inline] - fn parse_tree(self) -> ParseResult> { - Parser::new(self).parse() - } -} - -impl<'s> ParseTree<'s> for Vec> { - #[inline] - fn parse_tree(self) -> ParseResult> { - Parser::new(self.into_iter()).parse() - } -} - -/// Result type used for parsing. -type ParseResult = std::result::Result; - -/// 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> { +pub struct Parser<'s, T> where T: Iterator> { tokens: Peekable, state: ParserState, stack: Vec>, @@ -363,7 +231,7 @@ enum ParserState { impl<'s, T> Parser<'s, T> where T: Iterator> { /// 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> { } /// Parse into an abstract syntax tree. - fn parse(mut self) -> ParseResult> { + pub fn parse(mut self) -> ParseResult> { use ParserState as PS; while let Some(token) = self.tokens.next() { @@ -487,6 +355,24 @@ impl<'s, T> Parser<'s, T> where T: Iterator> { } } +/// Result type used for parsing. +type ParseResult = std::result::Result; + +/// 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) { - assert_eq!(src.tokenize().collect::>(), tokens); + assert_eq!(Tokens::new(src).collect::>(), 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 b23a13436..d5d3b081a 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; -} - -impl WritePdf for W { - #[inline] - fn write_pdf(&mut self, doc: &Document) -> PdfResult { - PdfCreator::new(self, doc)?.write() - } -} - -/// Result type used for parsing. -type PdfResult = std::result::Result; - -/// A failure while writing a _PDF_. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct PdfWritingError { - /// A message describing the error. - message: String, -} - -impl From for PdfWritingError { - #[inline] - fn from(err: io::Error) -> PdfWritingError { - PdfWritingError { message: format!("{}", err) } - } -} - -impl From for PdfWritingError { - #[inline] - fn from(err: opentype::Error) -> PdfWritingError { - PdfWritingError { message: format!("{}", err) } - } -} - -impl From 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 { + pub fn write(&mut self) -> PdfResult { // 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 = std::result::Result; -#[cfg(test)] -mod pdf_tests { - use super::*; - use crate::parsing::ParseTree; - use crate::engine::Typeset; +/// The error type for _PDF_ creation. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct PdfWritingError { + /// A message describing the error. + message: String, +} - /// 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 error::Error for 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. - "); +impl From for PdfWritingError { + #[inline] + fn from(err: io::Error) -> PdfWritingError { + PdfWritingError { message: format!("{}", err) } + } +} + +impl From for PdfWritingError { + #[inline] + fn from(err: opentype::Error) -> PdfWritingError { + PdfWritingError { message: format!("{}", err) } + } +} + +impl From 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) } } diff --git a/src/syntax.rs b/src/syntax.rs new file mode 100644 index 000000000..38e5a60eb --- /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>, +} + +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>, +} diff --git a/src/utility.rs b/src/utility.rs index 4e6bc7d9f..efe519c41 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> { 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();