summaryrefslogtreecommitdiff
path: root/src/font/mod.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-10-09 19:45:40 +0200
committerLaurenz <laurmaedje@gmail.com>2019-10-09 19:46:42 +0200
commitf22a3070001e9c8db6fcc7b83b036111a6559a3d (patch)
treea14c437a2ef71b08af5847c4f38330f668f724c2 /src/font/mod.rs
parentb96a7e0cf3c97463ecb746d859b675541a427774 (diff)
Extract into separate repository 🧱
Diffstat (limited to 'src/font/mod.rs')
-rw-r--r--src/font/mod.rs448
1 files changed, 0 insertions, 448 deletions
diff --git a/src/font/mod.rs b/src/font/mod.rs
deleted file mode 100644
index 976327d9..00000000
--- a/src/font/mod.rs
+++ /dev/null
@@ -1,448 +0,0 @@
-//! Font loading and subsetting.
-//!
-//! # Font handling
-//! To do the typesetting, the engine needs font data. However, 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.
-
-use std::collections::HashMap;
-use std::fs::{self, File};
-use std::io::{self, Cursor, Read, Seek, BufReader};
-use std::path::{Path, PathBuf};
-
-use opentype::{Error as OpentypeError, OpenTypeReader};
-use opentype::tables::{Header, Name, CharMap, HorizontalMetrics, Post, OS2};
-use opentype::types::{MacStyleFlags, NameEntry};
-use toml::map::Map as TomlMap;
-use toml::value::Value as TomlValue;
-
-use self::subset::Subsetter;
-use crate::size::Size;
-
-mod loader;
-mod subset;
-
-pub use loader::{FontLoader, FontQuery};
-
-
-/// A parsed _OpenType_ font program.
-#[derive(Debug, Clone)]
-pub struct Font {
- /// The name of the font.
- pub name: String,
- /// The complete, raw bytes of the font program.
- pub program: Vec<u8>,
- /// The mapping from character codes to glyph ids.
- pub mapping: HashMap<char, u16>,
- /// The widths of the glyphs indexed by glyph id.
- pub widths: Vec<Size>,
- /// The id of the fallback glyph.
- pub default_glyph: u16,
- /// The typesetting or exporting-relevant metrics of this font.
- pub metrics: FontMetrics,
-}
-
-/// Font metrics relevant to the typesetting or exporting processes.
-#[derive(Debug, Copy, Clone)]
-pub struct FontMetrics {
- /// Whether the font is italic.
- pub italic: bool,
- /// Whether font is monospace.
- pub monospace: bool,
- /// The angle of text in italics (in counter-clockwise degrees from vertical).
- pub italic_angle: f32,
- /// The extremal values [x_min, y_min, x_max, y_max] for all glyph bounding boxes.
- pub bounding_box: [Size; 4],
- /// The typographic ascender.
- pub ascender: Size,
- /// The typographic descender.
- pub descender: Size,
- /// The approximate height of capital letters.
- pub cap_height: Size,
- /// The weight class of the font (from 100 for thin to 900 for heavy).
- pub weight_class: u16,
-}
-
-impl Font {
- /// Create a `Font` from a raw font program.
- pub fn new(program: Vec<u8>) -> FontResult<Font> {
- let cursor = Cursor::new(&program);
- let mut reader = OpenTypeReader::new(cursor);
-
- // All of these tables are required by the OpenType specification,
- // so we do not really have to handle the case that they are missing.
- let head = reader.read_table::<Header>()?;
- let name = reader.read_table::<Name>()?;
- let os2 = reader.read_table::<OS2>()?;
- let cmap = reader.read_table::<CharMap>()?;
- let hmtx = reader.read_table::<HorizontalMetrics>()?;
- let post = reader.read_table::<Post>()?;
-
- // Create a conversion function between font units and sizes.
- let font_unit_ratio = 1.0 / (head.units_per_em as f32);
- let font_unit_to_size = |x| Size::pt(font_unit_ratio * x);
-
- let font_name = name
- .get_decoded(NameEntry::PostScriptName)
- .unwrap_or_else(|| "unknown".to_owned());
-
- let widths = hmtx.metrics.iter()
- .map(|m| font_unit_to_size(m.advance_width as f32)).collect();
-
- let metrics = FontMetrics {
- italic: head.mac_style.contains(MacStyleFlags::ITALIC),
- monospace: post.is_fixed_pitch,
- italic_angle: post.italic_angle.to_f32(),
- bounding_box: [
- font_unit_to_size(head.x_min as f32),
- font_unit_to_size(head.y_min as f32),
- font_unit_to_size(head.x_max as f32),
- font_unit_to_size(head.y_max as f32),
- ],
- ascender: font_unit_to_size(os2.s_typo_ascender as f32),
- descender: font_unit_to_size(os2.s_typo_descender as f32),
- cap_height: font_unit_to_size(os2.s_cap_height.unwrap_or(os2.s_typo_ascender) as f32),
- weight_class: os2.us_weight_class,
- };
-
- Ok(Font {
- name: font_name,
- program,
- mapping: cmap.mapping,
- widths,
- default_glyph: os2.us_default_char.unwrap_or(0),
- metrics,
- })
- }
-
- /// Encode a character into it's glyph id.
- #[inline]
- pub fn encode(&self, character: char) -> u16 {
- self.mapping.get(&character).map(|&g| g).unwrap_or(self.default_glyph)
- }
-
- /// Encode the given text into a vector of glyph ids.
- #[inline]
- pub fn encode_text(&self, text: &str) -> Vec<u8> {
- const BYTES_PER_GLYPH: usize = 2;
- let mut bytes = Vec::with_capacity(BYTES_PER_GLYPH * text.len());
- for c in text.chars() {
- let glyph = self.encode(c);
- bytes.push((glyph >> 8) as u8);
- bytes.push((glyph & 0xff) as u8);
- }
- bytes
- }
-
- /// Generate a subsetted version of this font.
- ///
- /// This version includes only the given `chars` and _OpenType_ `tables`.
- #[inline]
- pub fn subsetted<C, I, S>(&self, chars: C, tables: I) -> Result<Font, FontError>
- where
- C: IntoIterator<Item=char>,
- I: IntoIterator<Item=S>,
- S: AsRef<str>
- {
- Subsetter::subset(self, chars, tables)
- }
-}
-
-/// A type that provides fonts.
-pub trait FontProvider {
- /// Returns a font with the given info if this provider has one.
- 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, this is not guaranteed.
- fn available<'p>(&'p self) -> &'p [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: Read + Seek> FontData for T {}
-
-/// Classifies a font by listing the font classes it is part of.
-///
-/// All fonts with the same [`FontInfo`] are part of the same intersection
-/// of [font classes](FontClass).
-///
-/// This structure can be constructed conveniently through the [`font`] macro.
-#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-pub struct FontInfo {
- /// The font classes this font is part of.
- pub classes: Vec<FontClass>,
-}
-
-impl FontInfo {
- /// Create a new font info from a collection of classes.
- #[inline]
- pub fn new<I>(classes: I) -> FontInfo where I: IntoIterator<Item=FontClass> {
- FontInfo {
- classes: classes.into_iter().collect()
- }
- }
-}
-
-/// A class of fonts.
-///
-/// The set of all fonts can be classified into subsets of font classes like
-/// _serif_ or _bold_. This enum lists such subclasses.
-#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-pub enum FontClass {
- Serif,
- SansSerif,
- Monospace,
- Regular,
- Bold,
- Italic,
- /// A custom family like _Arial_ or _Times_.
- Family(String),
-}
-
-/// A macro to create [FontInfos](crate::font::FontInfo) easily.
-///
-/// Accepts an ordered list of font classes. Strings expressions are parsed
-/// into custom `Family`-variants and others can be named directly.
-///
-/// # Examples
-/// ```
-/// # use typeset::font;
-/// // Noto Sans in regular typeface.
-/// font!["NotoSans", "Noto", Regular, SansSerif];
-///
-/// // Noto Serif in italics and boldface.
-/// font!["NotoSerif", "Noto", Bold, Italic, Serif];
-///
-/// // Arial in italics.
-/// font!["Arial", Italic, SansSerif];
-///
-/// // Noto Emoji, which works in sans-serif and serif contexts.
-/// font!["NotoEmoji", "Noto", Regular, SansSerif, Serif, Monospace];
-/// ```
-#[macro_export]
-macro_rules! font {
- // Parse class list one by one.
- (@__cls $v:expr) => {};
- (@__cls $v:expr, $c:ident) => { $v.push($crate::font::FontClass::$c); };
- (@__cls $v:expr, $c:ident, $($tts:tt)*) => {
- font!(@__cls $v, $c);
- font!(@__cls $v, $($tts)*)
- };
- (@__cls $v:expr, $f:expr) => { $v.push( $crate::font::FontClass::Family($f.to_string())); };
- (@__cls $v:expr, $f:expr, $($tts:tt)*) => {
- font!(@__cls $v, $f);
- font!(@__cls $v, $($tts)*)
- };
-
- // Entry point
- ($($tts:tt)*) => {{
- let mut classes = Vec::new();
- font!(@__cls classes, $($tts)*);
- $crate::font::FontInfo { classes }
- }};
-}
-
-/// A font provider serving fonts from a folder on the local file system.
-#[derive(Debug)]
-pub struct FileSystemFontProvider {
- /// The base folder all other paths are relative to.
- base: PathBuf,
- /// Paths of the fonts relative to the `base` path.
- paths: Vec<PathBuf>,
- /// The info for the font with the same index in `paths`.
- infos: Vec<FontInfo>,
-}
-
-impl FileSystemFontProvider {
- /// Create a new provider serving fonts from a base path. The `fonts` iterator
- /// should contain paths of fonts relative to the base alongside matching
- /// infos for these fonts.
- ///
- /// # Example
- /// Serve the two fonts `NotoSans-Regular` and `NotoSans-Italic` from the local folder
- /// `../fonts`.
- /// ```
- /// # use typeset::{font::FileSystemFontProvider, font};
- /// FileSystemFontProvider::new("../fonts", vec![
- /// ("NotoSans-Regular.ttf", font!["NotoSans", Regular, SansSerif]),
- /// ("NotoSans-Italic.ttf", font!["NotoSans", Italic, SansSerif]),
- /// ]);
- /// ```
- pub fn new<B, I, P>(base: B, fonts: I) -> FileSystemFontProvider
- where
- B: Into<PathBuf>,
- I: IntoIterator<Item = (P, FontInfo)>,
- P: Into<PathBuf>,
- {
- let iter = fonts.into_iter();
-
- // Find out how long the iterator is at least, to reserve the correct
- // capacity for the vectors.
- let min = iter.size_hint().0;
- let mut paths = Vec::with_capacity(min);
- let mut infos = Vec::with_capacity(min);
-
- for (path, info) in iter {
- paths.push(path.into());
- infos.push(info);
- }
-
- FileSystemFontProvider {
- base: base.into(),
- paths,
- infos,
- }
- }
-
- /// Create a new provider from a font listing file.
- pub fn from_listing<P: AsRef<Path>>(file: P) -> FontResult<FileSystemFontProvider> {
- fn inv<S: ToString>(message: S) -> FontError {
- FontError::InvalidListing(message.to_string())
- }
-
- let file = file.as_ref();
- let base = file.parent()
- .ok_or_else(|| inv("expected listings file"))?;
-
- let bytes = fs::read(file)?;
- let map: TomlMap<String, toml::Value> = toml::de::from_slice(&bytes)
- .map_err(|err| inv(err))?;
-
- let mut paths = Vec::new();
- let mut infos = Vec::new();
-
- for value in map.values() {
- if let TomlValue::Table(table) = value {
- // Parse the string file key.
- paths.push(match table.get("file") {
- Some(TomlValue::String(s)) => PathBuf::from(s),
- _ => return Err(inv("expected file name")),
- });
-
- // Parse the array<string> classes key.
- infos.push(if let Some(TomlValue::Array(array)) = table.get("classes") {
- let mut classes = Vec::with_capacity(array.len());
- for class in array {
- classes.push(match class {
- TomlValue::String(class) => match class.as_str() {
- "Serif" => FontClass::Serif,
- "SansSerif" => FontClass::SansSerif,
- "Monospace" => FontClass::Monospace,
- "Regular" => FontClass::Regular,
- "Bold" => FontClass::Bold,
- "Italic" => FontClass::Italic,
- _ => FontClass::Family(class.to_string()),
- },
- _ => return Err(inv("expect font class string")),
- })
- }
- FontInfo { classes }
- } else {
- return Err(inv("expected font classes"));
- });
- } else {
- return Err(inv("expected file/classes table"));
- }
- }
-
- Ok(FileSystemFontProvider {
- base: base.to_owned(),
- paths,
- infos,
- })
- }
-}
-
-impl FontProvider for FileSystemFontProvider {
- #[inline]
- fn get(&self, info: &FontInfo) -> Option<Box<dyn FontData>> {
- let index = self.infos.iter().position(|c| c == info)?;
- let path = &self.paths[index];
- let full_path = self.base.join(path);
- let file = File::open(full_path).ok()?;
- Some(Box::new(BufReader::new(file)) as Box<dyn FontData>)
- }
-
- #[inline]
- fn available<'p>(&'p self) -> &'p [FontInfo] {
- &self.infos
- }
-}
-
-
-/// The error type for font operations.
-pub enum FontError {
- /// The font file is incorrect.
- InvalidFont(String),
- /// The font listing is incorrect.
- InvalidListing(String),
- /// A character requested for subsetting was not present in the source font.
- MissingCharacter(char),
- /// A requested or required table was not present.
- MissingTable(String),
- /// The table is unknown to the subsetting engine.
- UnsupportedTable(String),
- /// The font is not supported by the subsetting engine.
- UnsupportedFont(String),
- /// An I/O Error occured while reading the font program.
- Io(io::Error),
-}
-
-error_type! {
- err: FontError,
- res: FontResult,
- show: f => match err {
- FontError::InvalidFont(message) => write!(f, "invalid font: {}", message),
- FontError::InvalidListing(message) => write!(f, "invalid font listing: {}", message),
- FontError::MissingCharacter(c) => write!(f, "missing character: '{}'", c),
- FontError::MissingTable(table) => write!(f, "missing table: '{}'", table),
- FontError::UnsupportedTable(table) => write!(f, "unsupported table: {}", table),
- FontError::UnsupportedFont(message) => write!(f, "unsupported font: {}", message),
- FontError::Io(err) => write!(f, "io error: {}", err),
- },
- source: match err {
- FontError::Io(err) => Some(err),
- _ => None,
- },
- from: (io::Error, FontError::Io(err)),
- from: (OpentypeError, match err {
- OpentypeError::InvalidFont(message) => FontError::InvalidFont(message),
- OpentypeError::MissingTable(tag) => FontError::MissingTable(tag.to_string()),
- OpentypeError::Io(err) => FontError::Io(err),
- }),
-}
-
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- /// Tests the font info macro.
- #[test]
- fn font_macro() {
- use FontClass::*;
-
- assert_eq!(font!["NotoSans", "Noto", Regular, SansSerif], FontInfo {
- classes: vec![
- Family("NotoSans".to_owned()), Family("Noto".to_owned()),
- Regular, SansSerif
- ]
- });
-
- assert_eq!(font!["NotoSerif", Serif, Italic, "Noto"], FontInfo {
- classes: vec![
- Family("NotoSerif".to_owned()), Serif, Italic,
- Family("Noto".to_owned())
- ],
- });
- }
-}