From a34d725000791215f2793269c4bc3de7374420ff Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 1 Apr 2019 12:00:37 +0200 Subject: [PATCH] =?UTF-8?q?Use=20interior=20mutability=20for=20font=20load?= =?UTF-8?q?er=20=E2=9A=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + src/engine/mod.rs | 300 +++++++++++++++++++++++++--------------------- 2 files changed, 167 insertions(+), 134 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d7568c68a..0ab4fe2e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ opentype = { path = "../opentype" } unicode-segmentation = "1.2" unicode-xid = "0.1.0" byteorder = "1" +smallvec = "0.6.9" diff --git a/src/engine/mod.rs b/src/engine/mod.rs index d9d169656..2e7938ed2 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -3,6 +3,7 @@ use std::cell::{RefCell, Ref}; use std::collections::HashMap; 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}; @@ -65,10 +66,6 @@ impl<'t> Engine<'t> { // Flush the text buffer. self.write_buffered_text(); - let fonts = self.font_loader.into_fonts(); - - println!("fonts: {:?}", fonts.len()); - // Create a document with one page from the contents. Ok(Document { pages: vec![Page { @@ -78,10 +75,65 @@ impl<'t> Engine<'t> { commands: self.text_commands, }], }], - fonts, + fonts: self.font_loader.into_fonts(), }) } + /// Write a word. + fn write_word(&mut self, word: &str) -> TypeResult<()> { + // 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(); + } + + // 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) -> TypeResult<()> { + 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 @@ -98,7 +150,7 @@ impl<'t> Engine<'t> { // font size from the previous line. self.ctx.style.font_size * self.ctx.style.line_spacing - * self.font_loader.get_at(self.active_font).metrics.ascender + * self.get_font_at(self.active_font).metrics.ascender } else { self.current_max_vertical_move }; @@ -114,50 +166,6 @@ impl<'t> Engine<'t> { self.active_font = index; } - /// Write a word. - fn write_word(&mut self, word: &str) -> TypeResult<()> { - let width = self.width(word)?; - - // If this would overflow, we move to a new line and finally write the previous one. - if self.would_overflow(width) { - self.write_buffered_text(); - self.move_newline(); - } - - for c in word.chars() { - let (index, _) = self.get_font_for(c)?; - if index != self.active_font { - self.write_buffered_text(); - self.set_font(index); - } - self.current_text.push(c); - let char_width = self.char_width(c).unwrap(); - self.current_line_width += char_width; - } - - Ok(()) - } - - /// Write the space character: `' '`. - fn write_space(&mut self) -> TypeResult<()> { - let space_width = self.char_width(' ')?; - - 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)); - } - } - /// 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 @@ -165,30 +173,24 @@ impl<'t> Engine<'t> { self.current_line_width + width > max_width } - /// The width of a word when printed out. - fn width(&self, word: &str) -> TypeResult { - let mut width = Size::zero(); - for c in word.chars() { - width += self.char_width(c)?; - } - Ok(width) - } - - /// The width of a char when printed out. - fn char_width(&self, character: char) -> TypeResult { - let font = self.get_font_for(character)?.1; - Ok(font.widths[font.map(character) as usize] * self.ctx.style.font_size) - } - /// Load a font that has the character we need. fn get_font_for(&self, character: char) -> TypeResult<(usize, Ref)> { - let res = self.font_loader.get(FontQuery { + self.font_loader.get(FontQuery { families: &self.ctx.style.font_families, italic: false, bold: false, character, - }).ok_or_else(|| TypesetError::MissingFont)?; - Ok(res) + }).ok_or_else(|| TypesetError::MissingFont) + } + + /// Load a font at an index. + fn get_font_at(&self, index: usize) -> Ref { + 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.ctx.style.font_size } } @@ -197,65 +199,107 @@ struct FontLoader<'t> { /// The context containing the used font providers. context: &'t Context<'t>, /// All available fonts indexed by provider. - availables: Vec<&'t [FontInfo]>, - /// Allows to lookup fonts by their infos. - indices: RefCell>, + provider_fonts: Vec<&'t [FontInfo]>, + /// The internal state. + state: RefCell>, +} + +/// Internal state of the font loader (wrapped in a RefCell). +struct FontLoaderState<'t> { + /// The loaded fonts along with their external indices. + fonts: Vec<(Option, Font)>, /// Allows to retrieve cached results for queries. - matches: RefCell, usize>>, - /// All loaded fonts. - loaded: RefCell>, + query_cache: HashMap, usize>, + /// Allows to lookup fonts by their infos. + info_cache: HashMap<&'t FontInfo, usize>, /// Indexed by outside and indices maps to internal indices. - external: RefCell>, + inner_index: Vec, } impl<'t> FontLoader<'t> { /// Create a new font loader. pub fn new(context: &'t Context<'t>) -> FontLoader { - let availables = context.font_providers.iter() + let provider_fonts = context.font_providers.iter() .map(|prov| prov.available()).collect(); FontLoader { context, - availables, - indices: RefCell::new(HashMap::new()), - matches: RefCell::new(HashMap::new()), - loaded: RefCell::new(vec![]), - external: RefCell::new(vec![]), + provider_fonts, + state: RefCell::new(FontLoaderState { + query_cache: HashMap::new(), + info_cache: HashMap::new(), + inner_index: vec![], + fonts: vec![], + }), } } - /// Return the list of fonts. - pub fn into_fonts(self) -> Vec { - // FIXME: Don't clone here. - let fonts = self.loaded.into_inner(); - self.external.into_inner().into_iter().map(|index| fonts[index].clone()).collect() - } - /// Return the best matching font and it's index (if there is any) given the query. pub fn get(&self, query: FontQuery<'t>) -> Option<(usize, Ref)> { - if let Some(index) = self.matches.borrow().get(&query) { - let external = self.external.borrow().iter().position(|i| i == index).unwrap(); - return Some((external, self.get_at_internal(*index))); + // 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 through all available fonts and try to find one. + // Go over all font infos from all font providers that match the query. for family in query.families { - for (p, available) in self.availables.iter().enumerate() { - for info in available.iter() { - if Self::matches(query, &family, info) { - if let Some((index, font)) = self.try_load(info, p) { - if font.mapping.contains_key(&query.character) { - self.matches.borrow_mut().insert(query, index); + 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(); - let pos = self.external.borrow().iter().position(|&i| i == index); - let external = pos.unwrap_or_else(|| { - let external = self.external.borrow().len(); - self.external.borrow_mut().push(index); - external - }); + // 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()?; - return Some((external, font)); - } + // 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)); } } } @@ -266,37 +310,25 @@ impl<'t> FontLoader<'t> { } /// Return a loaded font at an index. Panics if the index is out of bounds. - pub fn get_at(&self, index: usize) -> Ref { - let internal = self.external.borrow()[index]; - self.get_at_internal(internal) + pub fn get_with_index(&self, index: usize) -> Ref { + let state = self.state.borrow(); + let internal = state.inner_index[index]; + Ref::map(state, |s| &s.fonts[internal].1) } - /// Try to load the font with the given info from the provider. - fn try_load(&self, info: &FontInfo, provider: usize) -> Option<(usize, Ref)> { - if let Some(index) = self.indices.borrow().get(info) { - return Some((*index, self.get_at_internal(*index))); - } + /// Return the list of fonts. + pub fn into_fonts(self) -> Vec { + // 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, + }); - if let Some(mut source) = self.context.font_providers[provider].get(info) { - let mut program = Vec::new(); - source.read_to_end(&mut program).ok()?; - - let font = Font::new(program).ok()?; - - let index = self.loaded.borrow().len(); - println!("loading at interal index: {}", index); - self.loaded.borrow_mut().push(font); - self.indices.borrow_mut().insert(info.clone(), index); - - Some((index, self.get_at_internal(index))) - } else { - None - } - } - - /// Return a loaded font at an internal index. Panics if the index is out of bounds. - fn get_at_internal(&self, index: usize) -> Ref { - Ref::map(self.loaded.borrow(), |loaded| &loaded[index]) + // 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.