Font providers 🚀+ better docs 📜

This commit is contained in:
Laurenz 2019-03-14 19:54:38 +01:00
parent 22ea09d9c1
commit 83dd762f67
5 changed files with 312 additions and 64 deletions

View File

@ -30,30 +30,38 @@ pub struct Style {
pub margin_bottom: Size,
/// A fallback list of font families to use.
pub font_families: Vec<String>,
pub font_families: Vec<FontFamily>,
/// The font size.
pub font_size: f32,
/// The line spacing (as a multiple of the font size).
pub line_spacing: f32,
}
/// A family of fonts (either generic or named).
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum FontFamily {
SansSerif,
Serif,
Monospace,
Named(String),
}
impl Default for Style {
fn default() -> Style {
use FontFamily::*;
Style {
// A4 paper
// A4 paper.
width: Size::from_mm(210.0),
height: Size::from_mm(297.0),
// A bit more on top and bottom
// Margins. A bit more on top and bottom.
margin_left: Size::from_cm(2.5),
margin_top: Size::from_cm(3.0),
margin_right: Size::from_cm(2.5),
margin_bottom: Size::from_cm(3.0),
// Default font family
font_families: (&[
"NotoSans", "NotoSansMath"
]).iter().map(ToString::to_string).collect(),
// Default font family, font size and line spacing.
font_families: vec![SansSerif, Serif, Monospace],
font_size: 12.0,
line_spacing: 1.25,
}

View File

@ -1,18 +1,19 @@
//! Core typesetting engine.
use std::io;
use std::error;
use std::fmt;
use crate::syntax::{SyntaxTree, Node};
use crate::doc::{Document, Style, Size, Page, Text, TextCommand};
use crate::font::Font;
use crate::doc::{Document, Size, Page, Text, TextCommand};
use crate::font::{Font, FontConfig, FontError};
use crate::Context;
/// The core typesetting engine, transforming an abstract syntax tree into a document.
#[derive(Debug, Clone)]
pub struct Engine<'s> {
pub(crate) struct Engine<'a> {
// Immutable
tree: &'s SyntaxTree<'s>,
style: Style,
tree: &'a SyntaxTree<'a>,
ctx: &'a Context<'a>,
// Mutable
fonts: Vec<Font>,
@ -22,12 +23,12 @@ pub struct Engine<'s> {
current_width: Size,
}
impl<'s> Engine<'s> {
impl<'a> Engine<'a> {
/// Create a new generator from a syntax tree.
pub fn new(tree: &'s SyntaxTree<'s>, style: Style) -> Engine<'s> {
pub fn new(tree: &'a SyntaxTree<'a>, context: &'a Context<'a>) -> Engine<'a> {
Engine {
style,
tree,
ctx: context,
fonts: Vec::new(),
active_font: 0,
text_commands: Vec::new(),
@ -39,21 +40,33 @@ impl<'s> Engine<'s> {
/// Generate the abstract document.
pub fn typeset(mut self) -> TypeResult<Document> {
// Load font defined by style
let font_family = self.style.font_families.first().unwrap();
let program = std::fs::read(format!("../fonts/{}-Regular.ttf", font_family)).unwrap();
let font = Font::new(program).unwrap();
let mut font = None;
let config = FontConfig::new(self.ctx.style.font_families.clone());
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 font = match font {
Some(font) => font,
None => return Err(TypesetError::MissingFont),
};
self.fonts.push(font);
self.active_font = 0;
// Move cursor to top-left position
self.text_commands.push(TextCommand::Move(
self.style.margin_left,
self.style.height - self.style.margin_top
self.ctx.style.margin_left,
self.ctx.style.height - self.ctx.style.margin_top
));
// Set the current font
self.text_commands.push(TextCommand::SetFont(0, self.style.font_size));
self.text_commands.push(TextCommand::SetFont(0, self.ctx.style.font_size));
// Iterate through the documents nodes.
for node in &self.tree.nodes {
@ -70,8 +83,8 @@ impl<'s> Engine<'s> {
// Create a page from the contents.
let page = Page {
width: self.style.width,
height: self.style.height,
width: self.ctx.style.width,
height: self.ctx.style.height,
text: vec![Text {
commands: self.text_commands,
}],
@ -88,8 +101,8 @@ impl<'s> Engine<'s> {
let width = self.width(word);
if self.would_overflow(width) {
let vertical_move = - self.style.font_size
* self.style.line_spacing
let vertical_move = - self.ctx.style.font_size
* self.ctx.style.line_spacing
* font.metrics.ascender;
self.text_commands.push(TextCommand::Move(Size::zero(), vertical_move));
@ -115,31 +128,51 @@ impl<'s> Engine<'s> {
fn width(&self, word: &str) -> Size {
let font = &self.fonts[self.active_font];
word.chars()
.map(|c| font.widths[font.map(c) as usize] * self.style.font_size)
.map(|c| font.widths[font.map(c) as usize] * self.ctx.style.font_size)
.sum()
}
fn would_overflow(&self, width: Size) -> bool {
let max_width = self.style.width
- self.style.margin_left
- self.style.margin_right;
let max_width = self.ctx.style.width
- self.ctx.style.margin_left
- self.ctx.style.margin_right;
self.current_width + width > max_width
}
}
/// Result type used for parsing.
/// Result type used for typesetting.
type TypeResult<T> = std::result::Result<T, TypesetError>;
/// The error type for typesetting.
pub enum TypesetError {}
pub enum TypesetError {
/// There was no suitable font.
MissingFont,
/// An error occured while gathering font data.
Font(FontError),
/// An I/O Error on occured while reading a font.
Io(io::Error),
}
impl error::Error for TypesetError {}
impl error::Error for TypesetError {
#[inline]
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
TypesetError::Font(err) => Some(err),
TypesetError::Io(err) => Some(err),
_ => None,
}
}
}
impl fmt::Display for TypesetError {
#[inline]
fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
Ok(())
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
TypesetError::MissingFont => write!(f, "missing font"),
TypesetError::Font(err) => write!(f, "font error: {}", err),
TypesetError::Io(err) => write!(f, "io error: {}", err),
}
}
}
@ -149,3 +182,17 @@ impl fmt::Debug for TypesetError {
fmt::Display::fmt(self, f)
}
}
impl From<io::Error> for TypesetError {
#[inline]
fn from(err: io::Error) -> TypesetError {
TypesetError::Io(err)
}
}
impl From<FontError> for TypesetError {
#[inline]
fn from(err: FontError) -> TypesetError {
TypesetError::Font(err)
}
}

View File

@ -1,14 +1,17 @@
//! Font loading, utility and subsetting.
#![macro_use]
use std::collections::HashMap;
use std::error;
use std::fmt;
use std::io::{self, Cursor, Seek, SeekFrom};
use std::path::{Path, PathBuf};
use std::io::{self, Cursor, Read, Seek, SeekFrom};
use byteorder::{BE, ReadBytesExt, WriteBytesExt};
use opentype::{Error as OpentypeError, OpenTypeReader, Outlines, TableRecord, Tag};
use opentype::tables::{Header, Name, CharMap, MaximumProfile, HorizontalMetrics, Post, OS2};
use opentype::tables::{MacStyleFlags, NameEntry};
use crate::doc::Size;
use crate::doc::{Size, FontFamily};
/// An font wrapper which allows to subset a font.
@ -586,6 +589,133 @@ 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 {}
/// 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::doc::FontFamily::Named($family.to_string())];
families.extend([$($crate::doc::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>
})
}
}
type FontResult<T> = Result<T, FontError>;
/// The error type for font operations.

View File

@ -1,24 +1,50 @@
//! Typeset is a library for compiling documents written in the
//! corresponding typesetting language into a typesetted document in an
//! output format like _PDF_.
//! The compiler for the _Typeset_ typesetting language 📜.
//!
//! # Compilation
//! - **Parsing:** The parsing step first transforms a plain string into an
//! [iterator of tokens](Tokens). Then the parser operates on that to
//! construct an abstract syntax tree. The structures describing the tree
//! can be found in the [`syntax`](syntax) module.
//! - **Typesetting:** The next step is to transform the syntax tree into an
//! abstract document representation. Types for these can be found in the
//! [`doc`](doc) module. This representation contains already the finished
//! layout, but is still portable.
//! - **Exporting:** The abstract document can then be exported into supported
//! formats. Currently the only supported format is _PDF_. In this step
//! the text is finally encoded into glyph indices and font data is
//! subsetted.
//!
//! # Fonts
//! 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 a flexible font configuration
//! specifying font families and styles. A font provider is a type implementing the
//! [`FontProvider`](crate::font::FontProvider) trait. For convenience there exists
//! the [`FileFontProvider`](crate::font::FileFontProvider) to serve fonts from a
//! local folder.
//!
//! # Example
//! This is an example of compiling a really simple document into _PDF_.
//! ```
//! use typeset::Compiler;
//! use std::fs::File;
//! use typeset::{Compiler, font::FileFontProvider, file_font};
//!
//! // Minimal source code for our document.
//! let src = "Hello World from Typeset!";
//! // Simple example source code.
//! let source = "Hello World from Typeset!";
//!
//! // Create an output file.
//! // Create a compiler with a font provider that provides one font.
//! 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),
//! ]));
//!
//! // Open an output file, compile and write to the file.
//! # /*
//! let mut file = std::fs::File::create("hello-typeset.pdf").unwrap();
//! let mut file = File::create("hello-typeset.pdf").unwrap();
//! # */
//! # let mut file = std::fs::File::create("../target/typeset-hello.pdf").unwrap();
//!
//! // Create a compiler and write the document into a file as a PDF.
//! let compiler = Compiler::new();
//! compiler.write_pdf(src, &mut file).unwrap();
//! # let mut file = File::create("../target/typeset-hello.pdf").unwrap();
//! compiler.write_pdf(source, &mut file).unwrap();
//! ```
pub mod syntax;
@ -36,33 +62,51 @@ pub use crate::pdf::PdfError;
use std::error;
use std::fmt;
use std::io::Write;
use crate::parsing::Parser;
use crate::syntax::SyntaxTree;
use crate::engine::Engine;
use crate::parsing::Parser;
use crate::doc::{Document, Style};
use crate::font::FontProvider;
use crate::engine::Engine;
use crate::pdf::PdfCreator;
/// Compiles source code into typesetted documents allowing to
/// retrieve results at various stages.
pub struct Compiler {
/// Style for typesetting.
style: Style,
pub struct Compiler<'p> {
context: Context<'p>,
}
impl Compiler {
struct Context<'p> {
/// Style for typesetting.
style: Style,
/// Font providers.
font_providers: Vec<Box<dyn FontProvider + 'p>>,
}
impl<'p> Compiler<'p> {
/// Create a new compiler from a document.
#[inline]
pub fn new() -> Compiler {
pub fn new() -> Compiler<'p> {
Compiler {
style: Style::default(),
context: Context {
style: Style::default(),
font_providers: Vec::new(),
}
}
}
/// Set the default style for typesetting.
#[inline]
pub fn style(&mut self, style: Style) -> &mut Self {
self.style = style;
self.context.style = style;
self
}
/// Add a font provider.
#[inline]
pub fn add_font_provider<P: 'p>(&mut self, provider: P) -> &mut Self
where P: FontProvider {
self.context.font_providers.push(Box::new(provider));
self
}
@ -82,7 +126,7 @@ impl Compiler {
#[inline]
pub fn typeset(&self, source: &str) -> Result<Document, Error> {
let tree = self.parse(source)?;
Engine::new(&tree, self.style.clone()).typeset().map_err(Into::into)
Engine::new(&tree, &self.context).typeset().map_err(Into::into)
}
/// Write the document as a _PDF_, returning how many bytes were written.
@ -156,13 +200,32 @@ impl From<PdfError> for Error {
#[cfg(test)]
mod test {
use std::fs::File;
use crate::Compiler;
use crate::font::FileFontProvider;
/// Create a pdf with a name from the source code.
fn test(name: &str, src: &str) {
// Create compiler
let mut compiler = Compiler::new();
let 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(provider);
// Open output file;
let path = format!("../target/typeset-pdf-{}.pdf", name);
let mut file = std::fs::File::create(path).unwrap();
Compiler::new().write_pdf(src, &mut file).unwrap();
let mut file = File::create(path).unwrap();
// Compile and output
compiler.write_pdf(src, &mut file).unwrap();
}
#[test]

View File

@ -278,7 +278,7 @@ impl std::ops::Deref for PdfFont {
}
}
/// Result type used for parsing.
/// Result type for _PDF_ creation.
type PdfResult<T> = std::result::Result<T, PdfError>;
/// The error type for _PDF_ creation.