summaryrefslogtreecommitdiff
path: root/src/engine
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine')
-rw-r--r--src/engine/loader.rs179
-rw-r--r--src/engine/mod.rs303
-rw-r--r--src/engine/size.rs147
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);