diff options
Diffstat (limited to 'src/engine')
| -rw-r--r-- | src/engine/loader.rs | 179 | ||||
| -rw-r--r-- | src/engine/mod.rs | 303 | ||||
| -rw-r--r-- | src/engine/size.rs | 147 |
3 files changed, 0 insertions, 629 deletions
diff --git a/src/engine/loader.rs b/src/engine/loader.rs deleted file mode 100644 index bae5f51a..00000000 --- a/src/engine/loader.rs +++ /dev/null @@ -1,179 +0,0 @@ -//! 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, -} diff --git a/src/engine/mod.rs b/src/engine/mod.rs deleted file mode 100644 index a396ed3a..00000000 --- a/src/engine/mod.rs +++ /dev/null @@ -1,303 +0,0 @@ -//! Core typesetting engine. - -use std::cell::Ref; -use std::mem::swap; - -use smallvec::SmallVec; - -use crate::doc::{Document, Page, Text, TextCommand}; -use crate::font::{Font, FontFamily, FontProvider, FontError}; -use crate::syntax::{SyntaxTree, Node}; -use loader::{FontLoader, FontQuery}; - -mod size; -mod loader; -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. -struct Engine<'a> { - // Input - tree: &'a SyntaxTree, - style: &'a Style, - - // Internal - font_loader: FontLoader<'a>, - - // Output - text_commands: Vec<TextCommand>, - - // Intermediates - active_font: usize, - current_text: String, - current_line_width: Size, - current_max_vertical_move: Size, - bold: bool, - italic: bool, -} - -impl<'a> Engine<'a> { - /// Create a new generator from a syntax tree. - fn new( - tree: &'a SyntaxTree, - style: &'a Style, - font_providers: &'a [Box<dyn FontProvider + 'a>] - ) -> Engine<'a> { - Engine { - tree, - style, - font_loader: FontLoader::new(font_providers), - text_commands: vec![], - active_font: std::usize::MAX, - current_text: String::new(), - current_line_width: Size::zero(), - current_max_vertical_move: Size::zero(), - italic: false, - bold: false, - } - } - - /// Generate the abstract document. - fn typeset(mut self) -> TypesetResult<Document> { - // Start by moving to a suitable position. - self.move_start(); - - // Iterate through the documents nodes. - for node in &self.tree.nodes { - match node { - Node::Text(text) => self.write_word(text)?, - Node::Space => self.write_space()?, - Node::Newline => { - self.write_buffered_text(); - self.move_newline(self.style.paragraph_spacing); - }, - - Node::ToggleItalics => self.italic = !self.italic, - Node::ToggleBold => self.bold = !self.bold, - - Node::ToggleMath => unimplemented!(), - Node::Func(_) => unimplemented!(), - } - } - - // Flush the text buffer. - self.write_buffered_text(); - - // Create a document with one page from the contents. - Ok(Document { - pages: vec![Page { - width: self.style.width, - height: self.style.height, - text: vec![Text { - commands: self.text_commands, - }], - }], - fonts: self.font_loader.into_fonts(), - }) - } - - /// Write a word. - fn write_word(&mut self, word: &str) -> TypesetResult<()> { - // Contains pairs of (characters, font_index, char_width). - let mut chars_with_widths = SmallVec::<[(char, usize, Size); 12]>::new(); - - // Find out which font to use for each character in the word and meanwhile - // calculate the width of the word. - let mut word_width = Size::zero(); - for c in word.chars() { - let (index, font) = self.get_font_for(c)?; - let width = self.char_width(c, &font); - word_width += width; - chars_with_widths.push((c, index, width)); - } - - // If this would overflow, we move to a new line and finally write the previous one. - if self.would_overflow(word_width) { - self.write_buffered_text(); - self.move_newline(1.0); - } - - // Finally write the word. - for (c, index, width) in chars_with_widths { - if index != self.active_font { - // If we will change the font, first write the remaining things. - self.write_buffered_text(); - self.set_font(index); - } - - self.current_text.push(c); - self.current_line_width += width; - } - - Ok(()) - } - - /// Write the space character: `' '`. - fn write_space(&mut self) -> TypesetResult<()> { - let space_width = self.char_width(' ', &self.get_font_for(' ')?.1); - if !self.would_overflow(space_width) && self.current_line_width > Size::zero() { - self.write_word(" ")?; - } - - Ok(()) - } - - /// Write a text command with the buffered text. - fn write_buffered_text(&mut self) { - if !self.current_text.is_empty() { - let mut current_text = String::new(); - swap(&mut self.current_text, &mut current_text); - self.text_commands.push(TextCommand::Text(current_text)); - } - } - - /// Move to the starting position defined by the style. - fn move_start(&mut self) { - // Move cursor to top-left position - self.text_commands.push(TextCommand::Move( - self.style.margin_left, - self.style.height - self.style.margin_top - )); - } - - /// Move to a new line. - fn move_newline(&mut self, factor: f32) { - if self.active_font == std::usize::MAX { - return; - } - - 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 - // font size from the previous line. - self.style.font_size - * self.style.line_spacing - * self.get_font_at(self.active_font).metrics.ascender - * factor - } else { - self.current_max_vertical_move - }; - - self.text_commands.push(TextCommand::Move(Size::zero(), -vertical_move)); - self.current_max_vertical_move = Size::zero(); - self.current_line_width = Size::zero(); - } - - /// Set the current font. - fn set_font(&mut self, index: usize) { - self.text_commands.push(TextCommand::SetFont(index, self.style.font_size)); - self.active_font = index; - } - - /// Whether the current line plus the extra `width` would overflow the line. - fn would_overflow(&self, width: Size) -> bool { - let max_width = self.style.width - - self.style.margin_left - self.style.margin_right; - self.current_line_width + width > max_width - } - - /// Load a font that has the character we need. - fn get_font_for(&self, character: char) -> TypesetResult<(usize, Ref<Font>)> { - self.font_loader.get(FontQuery { - families: &self.style.font_families, - italic: self.italic, - bold: self.bold, - character, - }).ok_or_else(|| TypesetError::MissingFont) - } - - /// Load a font at an index. - fn get_font_at(&self, index: usize) -> Ref<Font> { - self.font_loader.get_with_index(index) - } - - /// The width of a char in a specific font. - fn char_width(&self, character: char, font: &Font) -> Size { - font.widths[font.map(character) as usize] * self.style.font_size - } -} - -/// The context for typesetting a function. -#[derive(Debug)] -pub struct TypesetContext {} - -/// Default styles for typesetting. -#[derive(Debug, Clone, PartialEq)] -pub struct Style { - /// The width of the paper. - pub width: Size, - /// The height of the paper. - pub height: Size, - - /// The left margin of the paper. - pub margin_left: Size, - /// The top margin of the paper. - pub margin_top: Size, - /// The right margin of the paper. - pub margin_right: Size, - /// The bottom margin of the paper. - pub margin_bottom: Size, - - /// A fallback list of font families to use. - pub font_families: Vec<FontFamily>, - /// The font size. - pub font_size: f32, - /// The line spacing (as a multiple of the font size). - pub line_spacing: f32, - /// The spacing for paragraphs (as a multiple of the line spacing). - pub paragraph_spacing: f32, -} - -impl Default for Style { - fn default() -> Style { - use FontFamily::*; - Style { - // A4 paper. - width: Size::from_mm(210.0), - height: Size::from_mm(297.0), - - // Margins. A bit more on top and bottom. - margin_left: Size::from_cm(3.0), - margin_top: Size::from_cm(3.0), - margin_right: Size::from_cm(3.0), - margin_bottom: Size::from_cm(3.0), - - // Default font family, font size and line spacing. - font_families: vec![SansSerif, Serif, Monospace], - font_size: 11.0, - line_spacing: 1.25, - paragraph_spacing: 1.5, - } - } -} - -/// The error type for typesetting. -pub enum TypesetError { - /// There was no suitable font. - MissingFont, - /// An error occured while gathering font data. - Font(FontError), -} - -/// The result type for typesetting. -pub type TypesetResult<T> = Result<T, TypesetError>; - -error_type! { - err: TypesetError, - show: f => match err { - TypesetError::MissingFont => write!(f, "missing font"), - TypesetError::Font(err) => write!(f, "font error: {}", err), - }, - source: match err { - TypesetError::Font(err) => Some(err), - _ => None, - }, - from: (std::io::Error, TypesetError::Font(FontError::Io(err))), - from: (FontError, TypesetError::Font(err)), -} diff --git a/src/engine/size.rs b/src/engine/size.rs deleted file mode 100644 index bf79a3c4..00000000 --- a/src/engine/size.rs +++ /dev/null @@ -1,147 +0,0 @@ -use std::cmp::Ordering; -use std::fmt::{self, Display, Debug, Formatter}; -use std::iter::Sum; -use std::ops::*; - - -/// A general size (unit of length) type. -#[derive(Copy, Clone, PartialEq, Default)] -pub struct Size { - /// The size in typographic points (1/72 inches). - points: f32, -} - -impl Size { - /// Create an zeroed size. - #[inline] - pub fn zero() -> Size { Size { points: 0.0 } } - - /// Create a size from a number of points. - #[inline] - pub fn from_points(points: f32) -> Size { Size { points } } - - /// Create a size from a number of inches. - #[inline] - pub fn from_inches(inches: f32) -> Size { Size { points: 72.0 * inches } } - - /// Create a size from a number of millimeters. - #[inline] - pub fn from_mm(mm: f32) -> Size { Size { points: 2.83465 * mm } } - - /// Create a size from a number of centimeters. - #[inline] - pub fn from_cm(cm: f32) -> Size { Size { points: 28.3465 * cm } } - - /// Create a size from a number of points. - #[inline] - pub fn to_points(&self) -> f32 { self.points } - - /// Create a size from a number of inches. - #[inline] - pub fn to_inches(&self) -> f32 { self.points * 0.0138889 } - - /// Create a size from a number of millimeters. - #[inline] - pub fn to_mm(&self) -> f32 { self.points * 0.352778 } - - /// Create a size from a number of centimeters. - #[inline] - pub fn to_cm(&self) -> f32 { self.points * 0.0352778 } -} - -impl Display for Size { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}pt", self.points) - } -} - -impl Debug for Size { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Display::fmt(self, f) - } -} - -impl PartialOrd for Size { - #[inline] - fn partial_cmp(&self, other: &Size) -> Option<Ordering> { - self.points.partial_cmp(&other.points) - } -} - -impl Neg for Size { - type Output = Size; - - #[inline] - fn neg(self) -> Size { - Size { points: -self.points } - } -} - -impl Sum for Size { - #[inline] - fn sum<I>(iter: I) -> Size where I: Iterator<Item=Size> { - iter.fold(Size::zero(), Add::add) - } -} - -macro_rules! impl_reflexive { - ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident) => { - impl $trait for Size { - type Output = Size; - - #[inline] - fn $func(self, other: Size) -> Size { - Size { points: $trait::$func(self.points, other.points) } - } - } - - impl $assign_trait for Size { - #[inline] - fn $assign_func(&mut self, other: Size) { - $assign_trait::$assign_func(&mut self.points, other.points); - } - } - }; -} - -macro_rules! impl_num_back { - ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => { - impl $trait<$ty> for Size { - type Output = Size; - - #[inline] - fn $func(self, other: $ty) -> Size { - Size { points: $trait::$func(self.points, other as f32) } - } - } - - impl $assign_trait<$ty> for Size { - #[inline] - fn $assign_func(&mut self, other: $ty) { - $assign_trait::$assign_func(&mut self.points, other as f32); - } - } - }; -} - -macro_rules! impl_num_both { - ($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => { - impl_num_back!($trait, $func, $assign_trait, $assign_func, $ty); - - impl $trait<Size> for $ty { - type Output = Size; - - #[inline] - fn $func(self, other: Size) -> Size { - Size { points: $trait::$func(self as f32, other.points) } - } - } - }; -} - -impl_reflexive!(Add, add, AddAssign, add_assign); -impl_reflexive!(Sub, sub, SubAssign, sub_assign); -impl_num_both!(Mul, mul, MulAssign, mul_assign, f32); -impl_num_both!(Mul, mul, MulAssign, mul_assign, i32); -impl_num_back!(Div, div, DivAssign, div_assign, f32); -impl_num_back!(Div, div, DivAssign, div_assign, i32); |
