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.rs218
2 files changed, 216 insertions, 181 deletions
diff --git a/src/engine/loader.rs b/src/engine/loader.rs
new file mode 100644
index 00000000..bae5f51a
--- /dev/null
+++ b/src/engine/loader.rs
@@ -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,
+}
diff --git a/src/engine/mod.rs b/src/engine/mod.rs
index 65fcd9a1..a396ed3a 100644
--- a/src/engine/mod.rs
+++ b/src/engine/mod.rs
@@ -1,25 +1,32 @@
//! Core typesetting engine.
-use std::cell::{RefCell, Ref};
-use std::collections::HashMap;
+use std::cell::Ref;
use std::mem::swap;
use smallvec::SmallVec;
-use crate::syntax::{SyntaxTree, Node};
use crate::doc::{Document, Page, Text, TextCommand};
-use crate::font::{Font, FontFamily, FontInfo, FontError};
-use crate::Context;
+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.
-pub struct Engine<'a> {
+struct Engine<'a> {
// Input
tree: &'a SyntaxTree,
- ctx: &'a Context<'a>,
+ style: &'a Style,
// Internal
font_loader: FontLoader<'a>,
@@ -38,12 +45,15 @@ pub struct Engine<'a> {
impl<'a> Engine<'a> {
/// Create a new generator from a syntax tree.
- #[inline]
- pub fn new(tree: &'a SyntaxTree, context: &'a Context<'a>) -> Engine<'a> {
+ fn new(
+ tree: &'a SyntaxTree,
+ style: &'a Style,
+ font_providers: &'a [Box<dyn FontProvider + 'a>]
+ ) -> Engine<'a> {
Engine {
tree,
- ctx: context,
- font_loader: FontLoader::new(context),
+ style,
+ font_loader: FontLoader::new(font_providers),
text_commands: vec![],
active_font: std::usize::MAX,
current_text: String::new(),
@@ -55,7 +65,7 @@ impl<'a> Engine<'a> {
}
/// Generate the abstract document.
- pub fn typeset(mut self) -> TypesetResult<Document> {
+ fn typeset(mut self) -> TypesetResult<Document> {
// Start by moving to a suitable position.
self.move_start();
@@ -66,7 +76,7 @@ impl<'a> Engine<'a> {
Node::Space => self.write_space()?,
Node::Newline => {
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,
@@ -83,8 +93,8 @@ impl<'a> Engine<'a> {
// Create a document with one page from the contents.
Ok(Document {
pages: vec![Page {
- width: self.ctx.style.width,
- height: self.ctx.style.height,
+ width: self.style.width,
+ height: self.style.height,
text: vec![Text {
commands: self.text_commands,
}],
@@ -152,8 +162,8 @@ impl<'a> Engine<'a> {
fn move_start(&mut self) {
// Move cursor to top-left position
self.text_commands.push(TextCommand::Move(
- self.ctx.style.margin_left,
- self.ctx.style.height - self.ctx.style.margin_top
+ self.style.margin_left,
+ 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() {
// If max vertical move is still zero, the line is empty and we take the
// font size from the previous line.
- self.ctx.style.font_size
- * self.ctx.style.line_spacing
+ self.style.font_size
+ * self.style.line_spacing
* self.get_font_at(self.active_font).metrics.ascender
* factor
} else {
@@ -181,21 +191,21 @@ impl<'a> Engine<'a> {
/// Set the current font.
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;
}
/// Whether the current line plus the extra `width` would overflow the line.
fn would_overflow(&self, width: Size) -> bool {
- let max_width = self.ctx.style.width
- - self.ctx.style.margin_left - self.ctx.style.margin_right;
+ 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.ctx.style.font_families,
+ families: &self.style.font_families,
italic: self.italic,
bold: self.bold,
character,
@@ -209,167 +219,13 @@ impl<'a> Engine<'a> {
/// 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.ctx.style.font_size
+ font.widths[font.map(character) as usize] * self.style.font_size
}
}
-/// Serves matching fonts given a query.
-struct FontLoader<'a> {
- /// The context containing the used font providers.
- 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,
-}
+/// The context for typesetting a function.
+#[derive(Debug)]
+pub struct TypesetContext {}
/// Default styles for typesetting.
#[derive(Debug, Clone, PartialEq)]