summaryrefslogtreecommitdiff
path: root/src/font.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-05-24 12:24:10 +0200
committerLaurenz <laurmaedje@gmail.com>2019-05-24 12:24:10 +0200
commitb3734bbc046fe7b14cff54e2dae7014a71014777 (patch)
treed80e97d9f0694cb7e7f61869a1085e64e791a5b8 /src/font.rs
parente3215fa3b92574e2087c28b1d494d397e6819236 (diff)
Restructure engine into modular layouter 🍂
Diffstat (limited to 'src/font.rs')
-rw-r--r--src/font.rs238
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())),