Refactor font providing ⚙

This commit is contained in:
Laurenz 2019-03-30 18:47:17 +01:00
parent db96ecae94
commit e6e5aad7ce
6 changed files with 238 additions and 194 deletions

View File

@ -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<Page>,
@ -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<TextCommand>,
}
/// Different commands for rendering text.
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone)]
pub enum TextCommand {
/// Writing of the text.
Text(String),

View File

@ -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<Document> {
// 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.

View File

@ -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).

View File

@ -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

View File

@ -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<u8>) -> FontResult<Font> {
@ -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<C, I1, S1, I2, S2>(
pub fn subsetted<C, I, S>(
&self,
chars: C,
needed_tables: I1,
optional_tables: I2
needed_tables: I,
optional_tables: I,
) -> Result<Font, FontError>
where
C: IntoIterator<Item=char>,
I1: IntoIterator<Item=S1>, S1: AsRef<str>,
I2: IntoIterator<Item=S2>, S2: AsRef<str>
I: IntoIterator<Item=S>,
S: AsRef<str>,
{
let mut chars: Vec<char> = 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<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 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<FontFamily>,
/// 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<bool>,
/// If some, matches only bold/non-bold fonts, otherwise any.
pub bold: Option<bool>,
}
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<PathBuf>,
infos: Vec<FontInfo>,
}
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<B, I, P>(base: B, infos: I) -> FileSystemFontProvider
where
B: Into<PathBuf>,
I: IntoIterator<Item = (P, FontInfo)>,
P: Into<PathBuf>,
{
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<Box<dyn FontData>> {
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<FontData>)
}
fn available<'a>(&'a self) -> &'a [FontInfo] {
&self.infos
}
}
@ -584,141 +759,6 @@ impl<T> TakeInvalid<T> for Option<T> {
}
}
/// 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<Box<dyn FontSource>>;
}
/// 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<T> 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<FontFamily>,
/// If some, matches only italic/non-italic fonts, otherwise any.
pub italic: Option<bool>,
/// If some, matches only bold/non-bold fonts, otherwise any.
pub bold: Option<bool>,
}
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<FontFamily>) -> 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<FileFontDescriptor<'a>>,
}
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<P: 'a, I>(root: P, specs: I) -> FileFontProvider<'a>
where
I: IntoIterator<Item=FileFontDescriptor<'a>>,
P: Into<PathBuf>
{
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<FontFamily>,
/// 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<Box<dyn FontSource>> {
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<FontSource>
})
}
}
/// The error type for font operations.
pub enum FontError {
/// The font file is incorrect.

View File

@ -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