diff options
| author | Laurenz <laurmaedje@gmail.com> | 2019-05-24 12:24:10 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2019-05-24 12:24:10 +0200 |
| commit | b3734bbc046fe7b14cff54e2dae7014a71014777 (patch) | |
| tree | d80e97d9f0694cb7e7f61869a1085e64e791a5b8 /src/font.rs | |
| parent | e3215fa3b92574e2087c28b1d494d397e6819236 (diff) | |
Restructure engine into modular layouter 🍂
Diffstat (limited to 'src/font.rs')
| -rw-r--r-- | src/font.rs | 238 |
1 files changed, 206 insertions, 32 deletions
diff --git a/src/font.rs b/src/font.rs index 19b09b31..17331234 100644 --- a/src/font.rs +++ b/src/font.rs @@ -1,16 +1,18 @@ //! Font loading and transforming. //! //! # Font handling -//! To do the typesetting, the compiler needs font data. To be highly portable the compiler assumes -//! nothing about the environment. To still work with fonts, the consumer of this library has to -//! add _font providers_ to their compiler instance. These can be queried for font data given -//! flexible font filters specifying required font families and styles. A font provider is a type -//! implementing the [`FontProvider`](crate::font::FontProvider) trait. +//! To do the typesetting, the typesetting engine needs font data. To be highly portable the engine +//! itself assumes nothing about the environment. To still work with fonts, the consumer of this +//! library has to add _font providers_ to their typesetting instance. These can be queried for font +//! data given flexible font filters specifying required font families and styles. A font provider +//! is a type implementing the [`FontProvider`](crate::font::FontProvider) trait. //! -//! There is one [included font provider](crate::font::FileSystemFontProvider) that serves -//! fonts from a folder on the file system. +//! There is one [included font provider](crate::font::FileSystemFontProvider) that serves fonts +//! from a folder on the file system. +use std::cell::{RefCell, Ref}; use std::collections::HashMap; +use std::fmt::{self, Debug, Formatter}; use std::fs::File; use std::io::{self, Cursor, Read, Seek, SeekFrom, BufReader}; use std::path::PathBuf; @@ -20,7 +22,7 @@ use opentype::{Error as OpentypeError, OpenTypeReader, Outlines, TableRecord, Ta use opentype::tables::{Header, Name, CharMap, MaximumProfile, HorizontalMetrics, Post, OS2}; use opentype::global::{MacStyleFlags, NameEntry}; -use crate::engine::Size; +use crate::layout::Size; /// A loaded font, containing relevant information for typesetting. @@ -174,25 +176,6 @@ pub struct FontMetrics { pub weight_class: u16, } -/// A type that provides fonts. -pub trait FontProvider { - /// Returns the font with the given info if this provider has it. - fn get(&self, info: &FontInfo) -> Option<Box<dyn FontData>>; - - /// The available fonts this provider can serve. While these should generally be retrievable - /// through the `get` method, it is not guaranteed that a font info that is contained here - /// yields a `Some` value when passed into `get`. - fn available<'a>(&'a self) -> &'a [FontInfo]; -} - -/// A wrapper trait around `Read + Seek`. -/// -/// This type is needed because currently you can't make a trait object -/// with two traits, like `Box<dyn Read + Seek>`. -/// Automatically implemented for all types that are [`Read`] and [`Seek`]. -pub trait FontData: Read + Seek {} -impl<T> FontData for T where T: Read + Seek {} - /// Describes a font. /// /// Can be constructed conveniently with the [`font_info`] macro. @@ -285,6 +268,25 @@ pub enum FontFamily { Named(String), } +/// A type that provides fonts. +pub trait FontProvider { + /// Returns the font with the given info if this provider has it. + fn get(&self, info: &FontInfo) -> Option<Box<dyn FontData>>; + + /// The available fonts this provider can serve. While these should generally be retrievable + /// through the `get` method, it is not guaranteed that a font info that is contained here + /// yields a `Some` value when passed into `get`. + fn available<'a>(&'a self) -> &'a [FontInfo]; +} + +/// A wrapper trait around `Read + Seek`. +/// +/// This type is needed because currently you can't make a trait object +/// with two traits, like `Box<dyn Read + Seek>`. +/// Automatically implemented for all types that are [`Read`] and [`Seek`]. +pub trait FontData: Read + Seek {} +impl<T> FontData for T where T: Read + Seek {} + /// A font provider serving fonts from a folder on the local file system. pub struct FileSystemFontProvider { base: PathBuf, @@ -349,10 +351,182 @@ impl FontProvider for FileSystemFontProvider { } } -struct Subsetter<'d> { +/// Serves matching fonts given a query. +pub struct FontLoader<'p> { + /// The font providers. + providers: Vec<&'p (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<P: 'p>(providers: &'p [P]) -> FontLoader<'p> where P: AsRef<dyn FontProvider + 'p> { + let providers: Vec<_> = providers.iter().map(|p| p.as_ref()).collect(); + 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<'a> { + /// 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: &'a [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, +} + +struct Subsetter<'a> { // Original font - font: &'d Font, - reader: OpenTypeReader<Cursor<&'d [u8]>>, + font: &'a Font, + reader: OpenTypeReader<Cursor<&'a [u8]>>, outlines: Outlines, tables: Vec<TableRecord>, cmap: Option<CharMap>, @@ -366,7 +540,7 @@ struct Subsetter<'d> { body: Vec<u8>, } -impl<'d> Subsetter<'d> { +impl<'a> Subsetter<'a> { fn subset<I, S>(mut self, needed_tables: I, optional_tables: I) -> FontResult<Font> where I: IntoIterator<Item=S>, S: AsRef<str> { // Find out which glyphs to include based on which characters we want @@ -695,7 +869,7 @@ impl<'d> Subsetter<'d> { })) } - fn get_table_data(&self, tag: Tag) -> FontResult<&'d [u8]> { + fn get_table_data(&self, tag: Tag) -> FontResult<&'a [u8]> { let record = match self.tables.binary_search_by_key(&tag, |r| r.tag) { Ok(index) => &self.tables[index], Err(_) => return Err(FontError::MissingTable(tag.to_string())), |
