Unify parsing and typesetting functions. 🗳

This commit is contained in:
Laurenz 2019-05-20 11:36:31 +02:00
parent 5c66bac689
commit e3215fa3b9
8 changed files with 303 additions and 285 deletions

View File

@ -46,7 +46,7 @@ fn run() -> Result<(), Box<Error>> {
])); ]));
// Compile the source code with the compiler. // Compile the source code with the compiler.
let document = compiler.typeset(&src)?; let document = compiler.compile(&src)?;
// Export the document into a PDF file. // Export the document into a PDF file.

179
src/engine/loader.rs Normal file
View File

@ -0,0 +1,179 @@
//! Loading of fonts by queries.
use std::fmt::{self, Debug, Formatter};
use std::cell::{RefCell, Ref};
use std::collections::HashMap;
use crate::font::{Font, FontProvider, FontFamily, FontInfo};
/// Serves matching fonts given a query.
pub struct FontLoader<'p> {
/// The font providers.
providers: &'p [Box<dyn FontProvider + 'p>],
/// All available fonts indexed by provider.
provider_fonts: Vec<&'p [FontInfo]>,
/// The internal state.
state: RefCell<FontLoaderState<'p>>,
}
/// Internal state of the font loader (wrapped in a RefCell).
struct FontLoaderState<'p> {
/// The loaded fonts along with their external indices.
fonts: Vec<(Option<usize>, Font)>,
/// Allows to retrieve cached results for queries.
query_cache: HashMap<FontQuery<'p>, usize>,
/// Allows to lookup fonts by their infos.
info_cache: HashMap<&'p FontInfo, usize>,
/// Indexed by outside and indices maps to internal indices.
inner_index: Vec<usize>,
}
impl<'p> FontLoader<'p> {
/// Create a new font loader.
pub fn new(providers: &'p [Box<dyn FontProvider + 'p>]) -> FontLoader {
let provider_fonts = providers.iter()
.map(|prov| prov.available()).collect();
FontLoader {
providers,
provider_fonts,
state: RefCell::new(FontLoaderState {
query_cache: HashMap::new(),
info_cache: HashMap::new(),
inner_index: vec![],
fonts: vec![],
}),
}
}
/// Return the best matching font and it's index (if there is any) given the query.
pub fn get(&self, query: FontQuery<'p>) -> Option<(usize, Ref<Font>)> {
// Check if we had the exact same query before.
let state = self.state.borrow();
if let Some(&index) = state.query_cache.get(&query) {
// That this is the query cache means it must has an index as we've served it before.
let extern_index = state.fonts[index].0.unwrap();
let font = Ref::map(state, |s| &s.fonts[index].1);
return Some((extern_index, font));
}
drop(state);
// Go over all font infos from all font providers that match the query.
for family in query.families {
for (provider, infos) in self.providers.iter().zip(&self.provider_fonts) {
for info in infos.iter() {
// Check whether this info matches the query.
if Self::matches(query, family, info) {
let mut state = self.state.borrow_mut();
// Check if we have already loaded this font before.
// Otherwise we'll fetch the font from the provider.
let index = if let Some(&index) = state.info_cache.get(info) {
index
} else if let Some(mut source) = provider.get(info) {
// Read the font program into a vec.
let mut program = Vec::new();
source.read_to_end(&mut program).ok()?;
// Create a font from it.
let font = Font::new(program).ok()?;
// Insert it into the storage.
let index = state.fonts.len();
state.info_cache.insert(info, index);
state.fonts.push((None, font));
index
} else {
continue;
};
// Check whether this font has the character we need.
let has_char = state.fonts[index].1.mapping.contains_key(&query.character);
if has_char {
// We can take this font, so we store the query.
state.query_cache.insert(query, index);
// Now we have to find out the external index of it, or assign a new
// one if it has not already one.
let maybe_extern_index = state.fonts[index].0;
let extern_index = maybe_extern_index.unwrap_or_else(|| {
// We have to assign an external index before serving.
let extern_index = state.inner_index.len();
state.inner_index.push(index);
state.fonts[index].0 = Some(extern_index);
extern_index
});
// Release the mutable borrow and borrow immutably.
drop(state);
let font = Ref::map(self.state.borrow(), |s| &s.fonts[index].1);
// Finally we can return it.
return Some((extern_index, font));
}
}
}
}
}
None
}
/// Return a loaded font at an index. Panics if the index is out of bounds.
pub fn get_with_index(&self, index: usize) -> Ref<Font> {
let state = self.state.borrow();
let internal = state.inner_index[index];
Ref::map(state, |s| &s.fonts[internal].1)
}
/// Return the list of fonts.
pub fn into_fonts(self) -> Vec<Font> {
// Sort the fonts by external key so that they are in the correct order.
let mut fonts = self.state.into_inner().fonts;
fonts.sort_by_key(|&(maybe_index, _)| match maybe_index {
Some(index) => index as isize,
None => -1,
});
// Remove the fonts that are not used from the outside
fonts.into_iter().filter_map(|(maybe_index, font)| {
maybe_index.map(|_| font)
}).collect()
}
/// Check whether the query and the current family match the info.
fn matches(query: FontQuery, family: &FontFamily, info: &FontInfo) -> bool {
info.families.contains(family)
&& info.italic == query.italic && info.bold == query.bold
}
}
impl Debug for FontLoader<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let state = self.state.borrow();
f.debug_struct("FontLoader")
.field("providers", &self.providers.len())
.field("provider_fonts", &self.provider_fonts)
.field("fonts", &state.fonts)
.field("query_cache", &state.query_cache)
.field("info_cache", &state.info_cache)
.field("inner_index", &state.inner_index)
.finish()
}
}
/// A query for a font with specific properties.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct FontQuery<'p> {
/// A fallback list of font families to accept. The first family in this list, that also
/// satisfies the other conditions, shall be returned.
pub families: &'p [FontFamily],
/// Whether the font shall be in italics.
pub italic: bool,
/// Whether the font shall be in boldface.
pub bold: bool,
/// Which character we need.
pub character: char,
}

View File

@ -1,25 +1,32 @@
//! Core typesetting engine. //! Core typesetting engine.
use std::cell::{RefCell, Ref}; use std::cell::Ref;
use std::collections::HashMap;
use std::mem::swap; use std::mem::swap;
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::syntax::{SyntaxTree, Node};
use crate::doc::{Document, Page, Text, TextCommand}; use crate::doc::{Document, Page, Text, TextCommand};
use crate::font::{Font, FontFamily, FontInfo, FontError}; use crate::font::{Font, FontFamily, FontProvider, FontError};
use crate::Context; use crate::syntax::{SyntaxTree, Node};
use loader::{FontLoader, FontQuery};
mod size; mod size;
mod loader;
pub use size::Size; pub use size::Size;
/// Typeset a parsed syntax tree.
pub fn typeset<'p>(tree: &SyntaxTree, style: &Style, font_providers: &[Box<dyn FontProvider + 'p>])
-> TypesetResult<Document> {
Engine::new(tree, style, font_providers).typeset()
}
/// The core typesetting engine, transforming an abstract syntax tree into a document. /// The core typesetting engine, transforming an abstract syntax tree into a document.
pub struct Engine<'a> { struct Engine<'a> {
// Input // Input
tree: &'a SyntaxTree, tree: &'a SyntaxTree,
ctx: &'a Context<'a>, style: &'a Style,
// Internal // Internal
font_loader: FontLoader<'a>, font_loader: FontLoader<'a>,
@ -38,12 +45,15 @@ pub struct Engine<'a> {
impl<'a> Engine<'a> { impl<'a> Engine<'a> {
/// Create a new generator from a syntax tree. /// Create a new generator from a syntax tree.
#[inline] fn new(
pub fn new(tree: &'a SyntaxTree, context: &'a Context<'a>) -> Engine<'a> { tree: &'a SyntaxTree,
style: &'a Style,
font_providers: &'a [Box<dyn FontProvider + 'a>]
) -> Engine<'a> {
Engine { Engine {
tree, tree,
ctx: context, style,
font_loader: FontLoader::new(context), font_loader: FontLoader::new(font_providers),
text_commands: vec![], text_commands: vec![],
active_font: std::usize::MAX, active_font: std::usize::MAX,
current_text: String::new(), current_text: String::new(),
@ -55,7 +65,7 @@ impl<'a> Engine<'a> {
} }
/// Generate the abstract document. /// Generate the abstract document.
pub fn typeset(mut self) -> TypesetResult<Document> { fn typeset(mut self) -> TypesetResult<Document> {
// Start by moving to a suitable position. // Start by moving to a suitable position.
self.move_start(); self.move_start();
@ -66,7 +76,7 @@ impl<'a> Engine<'a> {
Node::Space => self.write_space()?, Node::Space => self.write_space()?,
Node::Newline => { Node::Newline => {
self.write_buffered_text(); self.write_buffered_text();
self.move_newline(self.ctx.style.paragraph_spacing); self.move_newline(self.style.paragraph_spacing);
}, },
Node::ToggleItalics => self.italic = !self.italic, Node::ToggleItalics => self.italic = !self.italic,
@ -83,8 +93,8 @@ impl<'a> Engine<'a> {
// Create a document with one page from the contents. // Create a document with one page from the contents.
Ok(Document { Ok(Document {
pages: vec![Page { pages: vec![Page {
width: self.ctx.style.width, width: self.style.width,
height: self.ctx.style.height, height: self.style.height,
text: vec![Text { text: vec![Text {
commands: self.text_commands, commands: self.text_commands,
}], }],
@ -152,8 +162,8 @@ impl<'a> Engine<'a> {
fn move_start(&mut self) { fn move_start(&mut self) {
// Move cursor to top-left position // Move cursor to top-left position
self.text_commands.push(TextCommand::Move( self.text_commands.push(TextCommand::Move(
self.ctx.style.margin_left, self.style.margin_left,
self.ctx.style.height - self.ctx.style.margin_top self.style.height - self.style.margin_top
)); ));
} }
@ -166,8 +176,8 @@ impl<'a> Engine<'a> {
let vertical_move = if self.current_max_vertical_move == Size::zero() { let vertical_move = if self.current_max_vertical_move == Size::zero() {
// If max vertical move is still zero, the line is empty and we take the // If max vertical move is still zero, the line is empty and we take the
// font size from the previous line. // font size from the previous line.
self.ctx.style.font_size self.style.font_size
* self.ctx.style.line_spacing * self.style.line_spacing
* self.get_font_at(self.active_font).metrics.ascender * self.get_font_at(self.active_font).metrics.ascender
* factor * factor
} else { } else {
@ -181,21 +191,21 @@ impl<'a> Engine<'a> {
/// Set the current font. /// Set the current font.
fn set_font(&mut self, index: usize) { fn set_font(&mut self, index: usize) {
self.text_commands.push(TextCommand::SetFont(index, self.ctx.style.font_size)); self.text_commands.push(TextCommand::SetFont(index, self.style.font_size));
self.active_font = index; self.active_font = index;
} }
/// Whether the current line plus the extra `width` would overflow the line. /// Whether the current line plus the extra `width` would overflow the line.
fn would_overflow(&self, width: Size) -> bool { fn would_overflow(&self, width: Size) -> bool {
let max_width = self.ctx.style.width let max_width = self.style.width
- self.ctx.style.margin_left - self.ctx.style.margin_right; - self.style.margin_left - self.style.margin_right;
self.current_line_width + width > max_width self.current_line_width + width > max_width
} }
/// Load a font that has the character we need. /// Load a font that has the character we need.
fn get_font_for(&self, character: char) -> TypesetResult<(usize, Ref<Font>)> { fn get_font_for(&self, character: char) -> TypesetResult<(usize, Ref<Font>)> {
self.font_loader.get(FontQuery { self.font_loader.get(FontQuery {
families: &self.ctx.style.font_families, families: &self.style.font_families,
italic: self.italic, italic: self.italic,
bold: self.bold, bold: self.bold,
character, character,
@ -209,167 +219,13 @@ impl<'a> Engine<'a> {
/// The width of a char in a specific font. /// The width of a char in a specific font.
fn char_width(&self, character: char, font: &Font) -> Size { fn char_width(&self, character: char, font: &Font) -> Size {
font.widths[font.map(character) as usize] * self.ctx.style.font_size font.widths[font.map(character) as usize] * self.style.font_size
} }
} }
/// Serves matching fonts given a query. /// The context for typesetting a function.
struct FontLoader<'a> { #[derive(Debug)]
/// The context containing the used font providers. pub struct TypesetContext {}
context: &'a Context<'a>,
/// All available fonts indexed by provider.
provider_fonts: Vec<&'a [FontInfo]>,
/// The internal state.
state: RefCell<FontLoaderState<'a>>,
}
/// Internal state of the font loader (wrapped in a RefCell).
struct FontLoaderState<'a> {
/// The loaded fonts along with their external indices.
fonts: Vec<(Option<usize>, Font)>,
/// Allows to retrieve cached results for queries.
query_cache: HashMap<FontQuery<'a>, usize>,
/// Allows to lookup fonts by their infos.
info_cache: HashMap<&'a FontInfo, usize>,
/// Indexed by outside and indices maps to internal indices.
inner_index: Vec<usize>,
}
impl<'a> FontLoader<'a> {
/// Create a new font loader.
pub fn new(context: &'a Context<'a>) -> FontLoader {
let provider_fonts = context.font_providers.iter()
.map(|prov| prov.available()).collect();
FontLoader {
context,
provider_fonts,
state: RefCell::new(FontLoaderState {
query_cache: HashMap::new(),
info_cache: HashMap::new(),
inner_index: vec![],
fonts: vec![],
}),
}
}
/// Return the best matching font and it's index (if there is any) given the query.
pub fn get(&self, query: FontQuery<'a>) -> Option<(usize, Ref<Font>)> {
// Check if we had the exact same query before.
let state = self.state.borrow();
if let Some(&index) = state.query_cache.get(&query) {
// That this is the query cache means it must has an index as we've served it before.
let extern_index = state.fonts[index].0.unwrap();
let font = Ref::map(state, |s| &s.fonts[index].1);
return Some((extern_index, font));
}
drop(state);
// Go over all font infos from all font providers that match the query.
for family in query.families {
for (provider, infos) in self.context.font_providers.iter().zip(&self.provider_fonts) {
for info in infos.iter() {
// Check whether this info matches the query.
if Self::matches(query, family, info) {
let mut state = self.state.borrow_mut();
// Check if we have already loaded this font before.
// Otherwise we'll fetch the font from the provider.
let index = if let Some(&index) = state.info_cache.get(info) {
index
} else if let Some(mut source) = provider.get(info) {
// Read the font program into a vec.
let mut program = Vec::new();
source.read_to_end(&mut program).ok()?;
// Create a font from it.
let font = Font::new(program).ok()?;
// Insert it into the storage.
let index = state.fonts.len();
state.info_cache.insert(info, index);
state.fonts.push((None, font));
index
} else {
continue;
};
// Check whether this font has the character we need.
let has_char = state.fonts[index].1.mapping.contains_key(&query.character);
if has_char {
// We can take this font, so we store the query.
state.query_cache.insert(query, index);
// Now we have to find out the external index of it, or assign a new
// one if it has not already one.
let maybe_extern_index = state.fonts[index].0;
let extern_index = maybe_extern_index.unwrap_or_else(|| {
// We have to assign an external index before serving.
let extern_index = state.inner_index.len();
state.inner_index.push(index);
state.fonts[index].0 = Some(extern_index);
extern_index
});
// Release the mutable borrow and borrow immutably.
drop(state);
let font = Ref::map(self.state.borrow(), |s| &s.fonts[index].1);
// Finally we can return it.
return Some((extern_index, font));
}
}
}
}
}
None
}
/// Return a loaded font at an index. Panics if the index is out of bounds.
pub fn get_with_index(&self, index: usize) -> Ref<Font> {
let state = self.state.borrow();
let internal = state.inner_index[index];
Ref::map(state, |s| &s.fonts[internal].1)
}
/// Return the list of fonts.
pub fn into_fonts(self) -> Vec<Font> {
// Sort the fonts by external key so that they are in the correct order.
let mut fonts = self.state.into_inner().fonts;
fonts.sort_by_key(|&(maybe_index, _)| match maybe_index {
Some(index) => index as isize,
None => -1,
});
// Remove the fonts that are not used from the outside
fonts.into_iter().filter_map(|(maybe_index, font)| {
maybe_index.map(|_| font)
}).collect()
}
/// Check whether the query and the current family match the info.
fn matches(query: FontQuery, family: &FontFamily, info: &FontInfo) -> bool {
info.families.contains(family)
&& info.italic == query.italic && info.bold == query.bold
}
}
/// A query for a font with specific properties.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
struct FontQuery<'a> {
/// A fallback list of font families to accept. The first family in this list, that also
/// satisfies the other conditions, shall be returned.
families: &'a [FontFamily],
/// Whether the font shall be in italics.
italic: bool,
/// Whether the font shall be in boldface.
bold: bool,
/// Which character we need.
character: char,
}
/// Default styles for typesetting. /// Default styles for typesetting.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]

View File

@ -209,7 +209,7 @@ pub struct FontInfo {
/// A macro to create [FontInfos](crate::font::FontInfo) easily. /// A macro to create [FontInfos](crate::font::FontInfo) easily.
/// ///
/// Accepts first a bracketed (ordered) list of font families. Allowed are string expressions /// Accepts first a bracketed (ordered) list of font families. Allowed are string expressions
/// aswell as the three base families `SansSerif`, `Serif` and `Monospace`. /// as well as the three base families `SansSerif`, `Serif` and `Monospace`.
/// ///
/// Then there may follow (separated by commas) the keywords `italic` and/or `bold`. /// Then there may follow (separated by commas) the keywords `italic` and/or `bold`.
/// ///

View File

@ -4,8 +4,9 @@ use std::any::Any;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use crate::syntax::{FuncHeader, Expression}; use crate::syntax::FuncHeader;
use crate::parsing::{FuncContext, ParseResult}; use crate::parsing::{ParseContext, ParseResult};
use crate::engine::{TypesetContext, TypesetResult};
/// Types that act as functions. /// Types that act as functions.
@ -17,10 +18,11 @@ use crate::parsing::{FuncContext, ParseResult};
/// used as functions, that is they fulfill the bounds `Debug + PartialEq + 'static`. /// used as functions, that is they fulfill the bounds `Debug + PartialEq + 'static`.
pub trait Function: FunctionBounds { pub trait Function: FunctionBounds {
/// Parse the tokens of the context with the given header and scope into self. /// Parse the tokens of the context with the given header and scope into self.
fn parse(context: FuncContext) -> ParseResult<Self> where Self: Sized; fn parse(header: &FuncHeader, body: Option<&str>, ctx: &ParseContext)
-> ParseResult<Self> where Self: Sized;
/// Execute the function and optionally yield a return value. /// Execute the function and optionally yield a return value.
fn typeset(&self, header: &FuncHeader) -> Option<Expression>; fn typeset(&self, ctx: &TypesetContext) -> TypesetResult<()>;
} }
impl PartialEq for dyn Function { impl PartialEq for dyn Function {
@ -61,7 +63,8 @@ pub struct Scope {
} }
/// A function which transforms a parsing context into a boxed function. /// A function which transforms a parsing context into a boxed function.
type ParseFunc = dyn Fn(FuncContext) -> ParseResult<Box<dyn Function>>; type ParseFunc = dyn Fn(&FuncHeader, Option<&str>, &ParseContext)
-> ParseResult<Box<dyn Function>>;
impl Scope { impl Scope {
/// Create a new empty scope. /// Create a new empty scope.
@ -69,12 +72,17 @@ impl Scope {
Scope { parsers: HashMap::new() } Scope { parsers: HashMap::new() }
} }
/// Create a new scope with the standard functions contained.
pub fn with_std() -> Scope {
Scope::new()
}
/// Add a function type to the scope with a given name. /// Add a function type to the scope with a given name.
pub fn add<F: Function + 'static>(&mut self, name: &str) { pub fn add<F: Function + 'static>(&mut self, name: &str) {
self.parsers.insert( self.parsers.insert(
name.to_owned(), name.to_owned(),
Box::new(|context| { Box::new(|h, b, c| {
F::parse(context).map(|func| Box::new(func) as Box<dyn Function>) F::parse(h, b, c).map(|func| Box::new(func) as Box<dyn Function>)
}) })
); );
} }

View File

@ -31,8 +31,8 @@
//! ("NotoEmoji-Regular.ttf", font_info!(["NotoEmoji", "Noto", SansSerif, Serif, Monospace])), //! ("NotoEmoji-Regular.ttf", font_info!(["NotoEmoji", "Noto", SansSerif, Serif, Monospace])),
//! ])); //! ]));
//! //!
//! // Compile the source code with the compiler. //! // Compile the source code into a document with the compiler.
//! let document = compiler.typeset(src).unwrap(); //! let document = compiler.compile(src).unwrap();
//! //!
//! // Export the document into a PDF file. //! // Export the document into a PDF file.
//! # /* //! # /*
@ -43,10 +43,8 @@
//! exporter.export(&document, file).unwrap(); //! exporter.export(&document, file).unwrap();
//! ``` //! ```
use std::fmt::{self, Debug, Formatter};
use crate::doc::Document; use crate::doc::Document;
use crate::engine::{Engine, Style, TypesetError}; use crate::engine::{typeset, Style, TypesetResult, TypesetError};
use crate::func::Scope; use crate::func::Scope;
use crate::font::FontProvider; use crate::font::FontProvider;
use crate::parsing::{parse, ParseResult, ParseError}; use crate::parsing::{parse, ParseResult, ParseError};
@ -68,7 +66,10 @@ pub mod syntax;
/// ///
/// Holds the compilation context, which can be configured through various methods. /// Holds the compilation context, which can be configured through various methods.
pub struct Compiler<'p> { pub struct Compiler<'p> {
context: Context<'p>, /// Style for typesetting.
style: Style,
/// Font providers.
font_providers: Vec<Box<dyn FontProvider + 'p>>,
} }
impl<'p> Compiler<'p> { impl<'p> Compiler<'p> {
@ -76,55 +77,42 @@ impl<'p> Compiler<'p> {
#[inline] #[inline]
pub fn new() -> Compiler<'p> { pub fn new() -> Compiler<'p> {
Compiler { Compiler {
context: Context { style: Style::default(),
style: Style::default(), font_providers: vec![],
font_providers: vec![],
}
} }
} }
/// Set the default style for the document. /// Set the default style for the document.
#[inline] #[inline]
pub fn set_style(&mut self, style: Style) { pub fn set_style(&mut self, style: Style) {
self.context.style = style; self.style = style;
} }
/// Add a font provider to the context of this compiler. /// Add a font provider to the context of this compiler.
#[inline] #[inline]
pub fn add_font_provider<P: 'p>(&mut self, provider: P) where P: FontProvider { pub fn add_font_provider<P: 'p>(&mut self, provider: P) where P: FontProvider {
self.context.font_providers.push(Box::new(provider)); self.font_providers.push(Box::new(provider));
} }
/// Parse source code into a syntax tree. /// Parse source code into a syntax tree.
#[inline] #[inline]
pub fn parse(&self, src: &str) -> ParseResult<SyntaxTree> { pub fn parse(&self, src: &str) -> ParseResult<SyntaxTree> {
let scope = Scope::new(); let scope = Scope::with_std();
parse(src, &scope) parse(src, &scope)
} }
/// Typeset a parsed syntax tree into a document.
#[inline]
pub fn typeset(&self, tree: &SyntaxTree) -> TypesetResult<Document> {
typeset(&tree, &self.style, &self.font_providers).map_err(Into::into)
}
/// Compile a portable typesetted document from source code. /// Compile a portable typesetted document from source code.
#[inline] #[inline]
pub fn typeset(&self, src: &str) -> CompileResult<Document> { pub fn compile(&self, src: &str) -> Result<Document, CompileError> {
let tree = self.parse(src)?; let tree = self.parse(src)?;
let engine = Engine::new(&tree, &self.context); let document = self.typeset(&tree)?;
engine.typeset().map_err(Into::into) Ok(document)
}
}
/// Holds the compilation context.
pub struct Context<'p> {
/// Style for typesetting.
style: Style,
/// Font providers.
font_providers: Vec<Box<dyn FontProvider + 'p>>,
}
impl Debug for Context<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("Context")
.field("style", &self.style)
.field("font_providers", &self.font_providers.len())
.finish()
} }
} }
@ -132,26 +120,23 @@ impl Debug for Context<'_> {
pub enum CompileError { pub enum CompileError {
/// An error that occured while transforming source code into /// An error that occured while transforming source code into
/// an abstract syntax tree. /// an abstract syntax tree.
Parse(ParseError), ParseErr(ParseError),
/// An error that occured while typesetting into an abstract document. /// An error that occured while typesetting into an abstract document.
Typeset(TypesetError), TypesetErr(TypesetError),
} }
/// The result type for compilation.
pub type CompileResult<T> = Result<T, CompileError>;
error_type! { error_type! {
err: CompileError, err: CompileError,
show: f => match err { show: f => match err {
CompileError::Parse(e) => write!(f, "parse error: {}", e), CompileError::ParseErr(e) => write!(f, "parse error: {}", e),
CompileError::Typeset(e) => write!(f, "typeset error: {}", e), CompileError::TypesetErr(e) => write!(f, "typeset error: {}", e),
}, },
source: match err { source: match err {
CompileError::Parse(e) => Some(e), CompileError::ParseErr(e) => Some(e),
CompileError::Typeset(e) => Some(e), CompileError::TypesetErr(e) => Some(e),
}, },
from: (ParseError, CompileError::Parse(err)), from: (ParseError, CompileError::ParseErr(err)),
from: (TypesetError, CompileError::Typeset(err)), from: (TypesetError, CompileError::TypesetErr(err)),
} }
@ -177,7 +162,7 @@ mod test {
])); ]));
// Compile into document // Compile into document
let document = compiler.typeset(src).unwrap(); let document = compiler.compile(src).unwrap();
// Write to file // Write to file
let path = format!("../target/typeset-unit-{}.pdf", name); let path = format!("../target/typeset-unit-{}.pdf", name);
@ -187,38 +172,30 @@ mod test {
} }
#[test] #[test]
fn simple() { fn features() {
test("parentheses", "Text with ) and ( or (enclosed) works."); test("features", r"
test("multiline-lorem"," **FEATURES TEST PAGE**
__Simple multiline:__
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam 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 voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
clita kasd gubergren, no sea takimata sanctus est. clita kasd gubergren, no sea takimata sanctus est.
__Parentheses:__ Text with ) and ( or (enclosed) works.
__Composite character:__
__Unicode:__ mbeed font with Unicode!
__Emoji:__ Hello World 🌍!
__Styles:__ This is **bold** and that is __great__!
"); ");
} }
#[test] #[test]
fn composite_glyph() { fn wikipedia() {
test("composite-glyph", "Composite character‼");
}
#[test]
fn unicode() {
test("unicode", "∑mbe∂∂ed font with Unicode!");
test("mixed-emoji", "Hello World 🌍!")
}
#[test]
fn styled() {
test("styled", "
**Hello World**.
That's __great__!
");
}
#[test]
fn long_wikipedia() {
test("wikipedia", r#" test("wikipedia", r#"
Typesetting is the composition of text by means of arranging physical types or the Typesetting is the composition of text by means of arranging physical types or the
digital equivalents. Stored letters and other symbols (called sorts in mechanical digital equivalents. Stored letters and other symbols (called sorts in mechanical

View File

@ -457,6 +457,10 @@ impl<'s> Parser<'s> {
let parser = self.scope.get_parser(&header.name) let parser = self.scope.get_parser(&header.name)
.ok_or_else(|| ParseError::new(format!("unknown function: '{}'", &header.name)))?; .ok_or_else(|| ParseError::new(format!("unknown function: '{}'", &header.name)))?;
let parse_context = ParseContext {
scope: &self.scope,
};
// Do the parsing dependent on whether the function has a body. // Do the parsing dependent on whether the function has a body.
Ok(if has_body { Ok(if has_body {
// Find out the string which makes the body of this function. // Find out the string which makes the body of this function.
@ -467,11 +471,7 @@ impl<'s> Parser<'s> {
// Parse the body. // Parse the body.
let body_string = &self.src[start .. end]; let body_string = &self.src[start .. end];
let body = parser(FuncContext { let body = parser(&header, Some(body_string), &parse_context)?;
header: &header,
body: Some(body_string),
scope: &self.scope,
})?;
// Skip to the end of the function in the token stream. // Skip to the end of the function in the token stream.
self.tokens.goto(end); self.tokens.goto(end);
@ -481,11 +481,7 @@ impl<'s> Parser<'s> {
body body
} else { } else {
parser(FuncContext { parser(&header, None, &parse_context)?
header: &header,
body: None,
scope: &self.scope,
})?
}) })
} }
@ -633,12 +629,8 @@ impl<'s> Iterator for PeekableTokens<'s> {
/// The context for parsing a function. /// The context for parsing a function.
#[derive(Debug)] #[derive(Debug)]
pub struct FuncContext<'s> { pub struct ParseContext<'s> {
/// The header of the function to be parsed. /// The scope containing function definitions.
pub header: &'s FuncHeader,
/// The body source if the function has a body, otherwise nothing.
pub body: Option<&'s str>,
/// The current scope containing function definitions.
pub scope: &'s Scope, pub scope: &'s Scope,
} }
@ -668,7 +660,8 @@ pub struct ParseError(String);
pub type ParseResult<T> = Result<T, ParseError>; pub type ParseResult<T> = Result<T, ParseError>;
impl ParseError { impl ParseError {
fn new<S: Into<String>>(message: S) -> ParseError { /// Create a new parse error with a message.
pub fn new<S: Into<String>>(message: S) -> ParseError {
ParseError(message.into()) ParseError(message.into())
} }
} }
@ -807,9 +800,10 @@ mod token_tests {
#[cfg(test)] #[cfg(test)]
mod parse_tests { mod parse_tests {
use super::*; use super::*;
use funcs::*;
use crate::func::{Function, Scope}; use crate::func::{Function, Scope};
use crate::engine::{TypesetContext, TypesetResult};
use Node::{Space as S, Newline as N, Func as F}; use Node::{Space as S, Newline as N, Func as F};
use funcs::*;
/// Two test functions, one which parses it's body as another syntax tree /// Two test functions, one which parses it's body as another syntax tree
/// and another one which does not expect a body. /// and another one which does not expect a body.
@ -821,14 +815,16 @@ mod parse_tests {
pub struct TreeFn(pub SyntaxTree); pub struct TreeFn(pub SyntaxTree);
impl Function for TreeFn { impl Function for TreeFn {
fn parse(context: FuncContext) -> ParseResult<Self> where Self: Sized { fn parse(_: &FuncHeader, body: Option<&str>, ctx: &ParseContext)
if let Some(src) = context.body { -> ParseResult<Self> where Self: Sized {
parse(src, context.scope).map(|tree| TreeFn(tree)) if let Some(src) = body {
parse(src, ctx.scope).map(|tree| TreeFn(tree))
} else { } else {
Err(ParseError::new("expected body for tree fn")) Err(ParseError::new("expected body for tree fn"))
} }
} }
fn typeset(&self, _header: &FuncHeader) -> Option<Expression> { None }
fn typeset(&self, _: &TypesetContext) -> TypesetResult<()> { Ok(()) }
} }
/// A testing function without a body. /// A testing function without a body.
@ -836,14 +832,16 @@ mod parse_tests {
pub struct BodylessFn; pub struct BodylessFn;
impl Function for BodylessFn { impl Function for BodylessFn {
fn parse(context: FuncContext) -> ParseResult<Self> where Self: Sized { fn parse(_: &FuncHeader, body: Option<&str>, _: &ParseContext)
if context.body.is_none() { -> ParseResult<Self> where Self: Sized {
if body.is_none() {
Ok(BodylessFn) Ok(BodylessFn)
} else { } else {
Err(ParseError::new("unexpected body for bodyless fn")) Err(ParseError::new("unexpected body for bodyless fn"))
} }
} }
fn typeset(&self, _header: &FuncHeader) -> Option<Expression> { None }
fn typeset(&self, _: &TypesetContext) -> TypesetResult<()> { Ok(()) }
} }
} }

View File

@ -97,6 +97,6 @@ pub struct FuncHeader {
pub kwargs: HashMap<String, Expression> pub kwargs: HashMap<String, Expression>
} }
/// A potentially unevaluated expression. /// A value expression.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Expression {} pub enum Expression {}