From e6e5aad7cef36a40a8d808fca02866649e464d87 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 30 Mar 2019 18:47:17 +0100 Subject: [PATCH] =?UTF-8?q?Refactor=20font=20providing=20=E2=9A=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/doc.rs | 8 +- src/engine/mod.rs | 21 ++- src/engine/size.rs | 2 +- src/export/pdf.rs | 4 +- src/font.rs | 372 +++++++++++++++++++++++++-------------------- src/lib.rs | 25 ++- 6 files changed, 238 insertions(+), 194 deletions(-) diff --git a/src/doc.rs b/src/doc.rs index 3ffc7f8cd..83a3b300b 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -5,7 +5,7 @@ use crate::engine::Size; /// A complete typesetted document, which can be exported. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Document { /// The pages of the document. pub pages: Vec, @@ -14,7 +14,7 @@ pub struct Document { } /// A page with text contents in a document. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Page { /// The width of the page. pub width: Size, @@ -25,14 +25,14 @@ pub struct Page { } /// A series of text command, that can be written on to a page. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Text { /// The text commands. pub commands: Vec, } /// Different commands for rendering text. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub enum TextCommand { /// Writing of the text. Text(String), diff --git a/src/engine/mod.rs b/src/engine/mod.rs index f121ac82e..baad9bac8 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -2,7 +2,7 @@ use crate::syntax::{SyntaxTree, Node}; use crate::doc::{Document, Page, Text, TextCommand}; -use crate::font::{Font, FontFamily, FontConfig, FontError}; +use crate::font::{Font, FontFamily, FontFilter, FontError}; use crate::Context; mod size; @@ -41,13 +41,18 @@ impl<'a> Engine<'a> { pub fn typeset(mut self) -> TypeResult { // Load font defined by style let mut font = None; - let config = FontConfig::new(self.ctx.style.font_families.clone()); + let filter = FontFilter::new(&self.ctx.style.font_families); for provider in &self.ctx.font_providers { - if let Some(mut source) = provider.provide(&config) { - let mut program = Vec::new(); - source.read_to_end(&mut program)?; - font = Some(Font::new(program)?); - break; + let available = provider.available(); + for info in available { + if filter.matches(info) { + if let Some(mut source) = provider.get(info) { + let mut program = Vec::new(); + source.read_to_end(&mut program)?; + font = Some(Font::new(program)?); + break; + } + } } } @@ -141,7 +146,7 @@ impl<'a> Engine<'a> { } } -/// Default styles for a document. +/// Default styles for typesetting. #[derive(Debug, Clone, PartialEq)] pub struct Style { /// The width of the paper. diff --git a/src/engine/size.rs b/src/engine/size.rs index a6624c570..f66641c00 100644 --- a/src/engine/size.rs +++ b/src/engine/size.rs @@ -4,7 +4,7 @@ use std::iter::Sum; use std::ops::*; -/// A general distance type that can convert between units. +/// A general size (unit of length) type. #[derive(Copy, Clone, PartialEq)] pub struct Size { /// The size in typographic points (1/72 inches). diff --git a/src/export/pdf.rs b/src/export/pdf.rs index cac60a878..a2260f74e 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -240,8 +240,8 @@ impl PdfFont { // Subset the font using the selected characters let subsetted = font.subsetted( chars.iter().cloned(), - &["head", "hhea", "maxp", "hmtx", "loca", "glyf"], - &["cvt ", "prep", "fpgm", /* "OS/2", "cmap", "name", "post" */], + &["head", "hhea", "maxp", "hmtx", "loca", "glyf"][..], + &["cvt ", "prep", "fpgm"][..], )?; // Specify flags for the font diff --git a/src/font.rs b/src/font.rs index 37346a3e3..7dbf20abd 100644 --- a/src/font.rs +++ b/src/font.rs @@ -1,7 +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. +//! +//! 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::path::{Path, PathBuf}; +use std::fs::File; +use std::path::PathBuf; use std::io::{self, Cursor, Read, Seek, SeekFrom}; use byteorder::{BE, ReadBytesExt, WriteBytesExt}; use opentype::{Error as OpentypeError, OpenTypeReader, Outlines, TableRecord, Tag}; @@ -10,8 +21,8 @@ use opentype::global::{MacStyleFlags, NameEntry}; use crate::engine::Size; -/// An font wrapper which allows to subset a font. -#[derive(Debug, Clone, PartialEq)] +/// A loaded font, containing relevant information for typesetting. +#[derive(Debug, Clone)] pub struct Font { /// The base name of the font. pub name: String, @@ -27,27 +38,6 @@ pub struct Font { pub metrics: FontMetrics, } -/// Font metrics relevant to the typesetting engine. -#[derive(Debug, Clone, PartialEq)] -pub struct FontMetrics { - /// Whether the font is italic. - pub is_italic: bool, - /// Whether font is fixed pitch. - pub is_fixed_pitch: bool, - /// The angle of italics. - pub italic_angle: f32, - /// The glyph bounding box: [x_min, y_min, x_max, y_max], - pub bounding_box: [Size; 4], - /// The typographics ascender relevant for line spacing. - pub ascender: Size, - /// The typographics descender relevant for line spacing. - pub descender: Size, - /// The approximate height of capital letters. - pub cap_height: Size, - /// The weight class of the font. - pub weight_class: u16, -} - impl Font { /// Create a new font from a font program. pub fn new(program: Vec) -> FontResult { @@ -125,16 +115,16 @@ impl Font { /// All needed tables will be included (returning an error if a table was not present /// in the source font) and optional tables will be included if there were present /// in the source font. All other tables will be dropped. - pub fn subsetted( + pub fn subsetted( &self, chars: C, - needed_tables: I1, - optional_tables: I2 + needed_tables: I, + optional_tables: I, ) -> Result where C: IntoIterator, - I1: IntoIterator, S1: AsRef, - I2: IntoIterator, S2: AsRef + I: IntoIterator, + S: AsRef, { let mut chars: Vec = chars.into_iter().collect(); chars.sort(); @@ -143,7 +133,7 @@ impl Font { let outlines = reader.outlines()?; let tables = reader.tables()?.to_vec(); - Subsetter { + let subsetter = Subsetter { font: &self, reader, outlines, @@ -155,7 +145,192 @@ impl Font { chars, records: Vec::new(), body: Vec::new(), - }.subset(needed_tables, optional_tables) + }; + + subsetter.subset(needed_tables, optional_tables) + } +} + +/// Font metrics relevant to the typesetting engine. +#[derive(Debug, Clone, PartialEq)] +pub struct FontMetrics { + /// Whether the font is italic. + pub is_italic: bool, + /// Whether font is fixed pitch. + pub is_fixed_pitch: bool, + /// The angle of italics. + pub italic_angle: f32, + /// The glyph bounding box: [x_min, y_min, x_max, y_max], + pub bounding_box: [Size; 4], + /// The typographics ascender relevant for line spacing. + pub ascender: Size, + /// The typographics descender relevant for line spacing. + pub descender: Size, + /// The approximate height of capital letters. + pub cap_height: Size, + /// The weight class of the font. + pub weight_class: u16, +} + +/// A type that provides fonts matching given criteria. +pub trait FontProvider { + /// Returns the font with the given info if this provider has it. + fn get(&self, info: &FontInfo) -> Option>; + + /// 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`. +/// Automatically implemented for all types that are [`Read`] and [`Seek`]. +pub trait FontData: Read + Seek {} +impl FontData for T where T: Read + Seek {} + +/// Describes a font. +/// +/// Can be constructed conventiently with the [`font_info`] macro. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct FontInfo { + /// The font families this font is part of. + pub families: Vec, + /// Whether the font is in italics. + pub italic: bool, + /// Whether the font is bold. + pub bold: bool, +} + +/// A macro to create [FontInfos](crate::font::FontInfo) easily. +/// +/// ``` +/// # use typeset::font_info; +/// font_info!( +/// "NotoSans", // Font family name +/// [SansSerif], // Generic families +/// false, false // Bold & Italic +/// ); +/// ``` +#[macro_export] +macro_rules! font_info { + ($family:expr, [$($generic:ident),*], $bold:expr, $italic:expr) => {{ + let mut families = vec![$crate::font::FontFamily::Named($family.to_string())]; + families.extend([$($crate::font::FontFamily::$generic),*].iter().cloned()); + $crate::font::FontInfo { + families, + italic: $italic, + bold: $bold, + } + }}; +} + +/// Criteria to filter fonts. +#[derive(Debug, Clone, PartialEq)] +pub struct FontFilter<'a> { + /// A fallback list of font families we accept. The first family in this list, that also + /// satisfies the other conditions shall be returned. + pub families: &'a [FontFamily], + /// If some, matches only italic/non-italic fonts, otherwise any. + pub italic: Option, + /// If some, matches only bold/non-bold fonts, otherwise any. + pub bold: Option, +} + +impl<'a> FontFilter<'a> { + /// Create a new font config with the given families. + /// + /// All other fields are set to [`None`] and match anything. + pub fn new(families: &'a [FontFamily]) -> FontFilter<'a> { + FontFilter { + families, + italic: None, + bold: None, + } + } + + /// Whether this filter matches the given info. + pub fn matches(&self, info: &FontInfo) -> bool { + self.italic.map(|i| i == info.italic).unwrap_or(true) + && self.bold.map(|i| i == info.bold).unwrap_or(true) + && self.families.iter().any(|family| info.families.contains(family)) + } + + /// Set the italic value to something. + pub fn italic(&mut self, italic: bool) -> &mut Self { + self.italic = Some(italic); self + } + + /// Set the bold value to something. + pub fn bold(&mut self, bold: bool) -> &mut Self { + self.bold = Some(bold); self + } +} + +/// A family of fonts (either generic or named). +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum FontFamily { + SansSerif, + Serif, + Monospace, + Named(String), +} + +/// A font provider serving fonts from a folder on the local file system. +pub struct FileSystemFontProvider { + base: PathBuf, + paths: Vec, + infos: Vec, +} + +impl FileSystemFontProvider { + /// Create a new provider from a folder and an iterator of pairs of + /// font paths and font infos. + /// + /// # Example + /// Serve the two fonts `NotoSans-Regular` and `NotoSans-Italic` from the local + /// folder `../fonts`. + /// ``` + /// # use typeset::{font::FileSystemFontProvider, font_info}; + /// FileSystemFontProvider::new("../fonts", vec![ + /// ("NotoSans-Regular.ttf", font_info!("NotoSans", [SansSerif], false, false)), + /// ("NotoSans-Italic.ttf", font_info!("NotoSans", [SansSerif], false, true)), + /// ]); + /// ``` + pub fn new(base: B, infos: I) -> FileSystemFontProvider + where + B: Into, + I: IntoIterator, + P: Into, + { + let mut paths = Vec::new(); + let mut font_infos = Vec::new(); + + for (path, info) in infos.into_iter() { + paths.push(path.into()); + font_infos.push(info); + } + + FileSystemFontProvider { + base: base.into(), + paths, + infos: font_infos, + } + } +} + +impl FontProvider for FileSystemFontProvider { + fn get(&self, info: &FontInfo) -> Option> { + let index = self.infos.iter().position(|i| i == info)?; + let path = &self.paths[index]; + let file = File::open(self.base.join(path)).ok()?; + Some(Box::new(file) as Box) + } + + fn available<'a>(&'a self) -> &'a [FontInfo] { + &self.infos } } @@ -584,141 +759,6 @@ impl TakeInvalid for Option { } } -/// A type that provides fonts matching given criteria. -pub trait FontProvider { - /// Returns a font matching the configuration - /// if this provider has a matching font. - fn provide(&self, config: &FontConfig) -> Option>; -} - -/// A wrapper trait around `Read + Seek` to allow for making a trait object. -/// -/// Automatically implemented for all types that are [`Read`] and [`Seek`]. -pub trait FontSource: Read + Seek {} - -impl FontSource for T where T: Read + Seek {} - -/// A family of fonts (either generic or named). -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum FontFamily { - SansSerif, - Serif, - Monospace, - Named(String), -} - -/// Criteria to filter fonts. -#[derive(Debug, Clone, PartialEq)] -pub struct FontConfig { - /// The font families we are looking for. - pub families: Vec, - /// If some, matches only italic/non-italic fonts, otherwise any. - pub italic: Option, - /// If some, matches only bold/non-bold fonts, otherwise any. - pub bold: Option, -} - -impl FontConfig { - /// Create a new font config with the given families. - /// - /// All other fields are set to [`None`] and match anything. - pub fn new(families: Vec) -> FontConfig { - FontConfig { - families, - italic: None, - bold: None, - } - } - - /// Set the italic value to something. - pub fn italic(&mut self, italic: bool) -> &mut Self { - self.italic = Some(italic); - self - } - - /// Set the bold value to something. - pub fn bold(&mut self, bold: bool) -> &mut Self { - self.bold = Some(bold); - self - } -} - -/// A font provider that works on font files on the local file system. -pub struct FileFontProvider<'a> { - root: PathBuf, - specs: Vec>, -} - -impl<'a> FileFontProvider<'a> { - /// Create a new file font provider. The folder relative to which the `specs` - /// contains the file paths, is given as `root`. - pub fn new(root: P, specs: I) -> FileFontProvider<'a> - where - I: IntoIterator>, - P: Into - { - FileFontProvider { - root: root.into(), - specs: specs.into_iter().collect() - } - } -} - -/// A type describing a font on the file system. -/// -/// Can be constructed conventiently with the [`file_font`] macro. -pub struct FileFontDescriptor<'a> { - /// The path to the font relative to the root. - pub path: &'a Path, - /// The font families this font is part of. - pub families: Vec, - /// Whether the font is in italics. - pub italic: bool, - /// Whether the font is bold. - pub bold: bool, -} - -impl FileFontDescriptor<'_> { - fn matches(&self, config: &FontConfig) -> bool { - config.italic.map(|i| i == self.italic).unwrap_or(true) - && config.bold.map(|i| i == self.bold).unwrap_or(true) - && config.families.iter().any(|family| self.families.contains(family)) - } -} - -/// Helper macro to create [file font descriptors](crate::font::FileFontDescriptor). -/// -/// ``` -/// # use typeset::file_font; -/// file_font!( -/// "NotoSans", // Font family name -/// [SansSerif], // Generic families -/// "NotoSans-Regular.ttf", // Font file -/// false, false // Bold & Italic -/// ); -/// ``` -#[macro_export] -macro_rules! file_font { - ($family:expr, [$($generic:ident),*], $path:expr, $bold:expr, $italic:expr) => {{ - let mut families = vec![$crate::font::FontFamily::Named($family.to_string())]; - families.extend([$($crate::font::FontFamily::$generic),*].iter().cloned()); - $crate::font::FileFontDescriptor { - path: std::path::Path::new($path), - families, - italic: $italic, bold: $bold, - } - }}; -} - -impl FontProvider for FileFontProvider<'_> { - fn provide(&self, config: &FontConfig) -> Option> { - self.specs.iter().find(|spec| spec.matches(&config)).map(|spec| { - let file = std::fs::File::open(self.root.join(spec.path)).unwrap(); - Box::new(file) as Box - }) - } -} - /// The error type for font operations. pub enum FontError { /// The font file is incorrect. diff --git a/src/lib.rs b/src/lib.rs index 9f37efc2e..fb81949a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,7 @@ //! ``` //! use std::fs::File; //! use typeset::Compiler; -//! use typeset::{font::FileFontProvider, file_font}; +//! use typeset::{font::FileSystemFontProvider, font_info}; //! use typeset::export::pdf::PdfExporter; //! //! // Simple example source code. @@ -24,9 +24,9 @@ //! // Create a compiler with a font provider that provides three fonts //! // (the default sans-serif fonts and a fallback for the emoji). //! let mut compiler = Compiler::new(); -//! compiler.add_font_provider(FileFontProvider::new("../fonts", vec![ +//! compiler.add_font_provider(FileSystemFontProvider::new("../fonts", vec![ //! // Font family name, generic families, file, bold, italic -//! file_font!("NotoSans", [SansSerif], "NotoSans-Regular.ttf", false, false), +//! ("NotoSans-Regular.ttf", font_info!("NotoSans", [SansSerif], false, false)), //! ])); //! //! // Compile the source code with the compiler. @@ -145,21 +145,20 @@ mod test { use std::fs::File; use crate::Compiler; use crate::export::pdf::PdfExporter; - use crate::font::FileFontProvider; + use crate::font::FileSystemFontProvider; /// Create a pdf with a name from the source code. fn test(name: &str, src: &str) { // Create compiler let mut compiler = Compiler::new(); - compiler.add_font_provider(FileFontProvider::new("../fonts", vec![ - // Font family name, generic families, file, bold, italic - file_font!("NotoSans", [SansSerif], "NotoSans-Regular.ttf", false, false), - file_font!("NotoSans", [SansSerif], "NotoSans-Bold.ttf", true, false), - file_font!("NotoSans", [SansSerif], "NotoSans-Italic.ttf", false, true), - file_font!("NotoSans", [SansSerif], "NotoSans-BoldItalic.ttf", true, true), - file_font!("NotoSansMath", [SansSerif], "NotoSansMath-Regular.ttf", false, false), - file_font!("NotoEmoji", [SansSerif, Serif, Monospace], - "NotoEmoji-Regular.ttf", false, false), + compiler.add_font_provider(FileSystemFontProvider::new("../fonts", vec![ + ("NotoSans-Regular.ttf", font_info!("NotoSans", [SansSerif], false, false)), + ("NotoSans-Bold.ttf", font_info!("NotoSans", [SansSerif], true, false)), + ("NotoSans-Italic.ttf", font_info!("NotoSans", [SansSerif], false, true)), + ("NotoSans-BoldItalic.ttf", font_info!("NotoSans", [SansSerif], true, true)), + ("NotoSansMath-Regular.ttf", font_info!("NotoSansMath", [SansSerif], false, false)), + ("NotoEmoji-Regular.ttf", + font_info!("NotoEmoji", [SansSerif, Serif, Monospace], false, false)), ])); // Compile into document