mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Font providers 🚀+ better docs 📜
This commit is contained in:
parent
22ea09d9c1
commit
83dd762f67
22
src/doc.rs
22
src/doc.rs
@ -30,30 +30,38 @@ pub struct Style {
|
|||||||
pub margin_bottom: Size,
|
pub margin_bottom: Size,
|
||||||
|
|
||||||
/// A fallback list of font families to use.
|
/// A fallback list of font families to use.
|
||||||
pub font_families: Vec<String>,
|
pub font_families: Vec<FontFamily>,
|
||||||
/// The font size.
|
/// The font size.
|
||||||
pub font_size: f32,
|
pub font_size: f32,
|
||||||
/// The line spacing (as a multiple of the font size).
|
/// The line spacing (as a multiple of the font size).
|
||||||
pub line_spacing: f32,
|
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 {
|
impl Default for Style {
|
||||||
fn default() -> Style {
|
fn default() -> Style {
|
||||||
|
use FontFamily::*;
|
||||||
Style {
|
Style {
|
||||||
// A4 paper
|
// A4 paper.
|
||||||
width: Size::from_mm(210.0),
|
width: Size::from_mm(210.0),
|
||||||
height: Size::from_mm(297.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_left: Size::from_cm(2.5),
|
||||||
margin_top: Size::from_cm(3.0),
|
margin_top: Size::from_cm(3.0),
|
||||||
margin_right: Size::from_cm(2.5),
|
margin_right: Size::from_cm(2.5),
|
||||||
margin_bottom: Size::from_cm(3.0),
|
margin_bottom: Size::from_cm(3.0),
|
||||||
|
|
||||||
// Default font family
|
// Default font family, font size and line spacing.
|
||||||
font_families: (&[
|
font_families: vec![SansSerif, Serif, Monospace],
|
||||||
"NotoSans", "NotoSansMath"
|
|
||||||
]).iter().map(ToString::to_string).collect(),
|
|
||||||
font_size: 12.0,
|
font_size: 12.0,
|
||||||
line_spacing: 1.25,
|
line_spacing: 1.25,
|
||||||
}
|
}
|
||||||
|
103
src/engine.rs
103
src/engine.rs
@ -1,18 +1,19 @@
|
|||||||
//! Core typesetting engine.
|
//! Core typesetting engine.
|
||||||
|
|
||||||
|
use std::io;
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use crate::syntax::{SyntaxTree, Node};
|
use crate::syntax::{SyntaxTree, Node};
|
||||||
use crate::doc::{Document, Style, Size, Page, Text, TextCommand};
|
use crate::doc::{Document, Size, Page, Text, TextCommand};
|
||||||
use crate::font::Font;
|
use crate::font::{Font, FontConfig, FontError};
|
||||||
|
use crate::Context;
|
||||||
|
|
||||||
|
|
||||||
/// The core typesetting engine, transforming an abstract syntax tree into a document.
|
/// The core typesetting engine, transforming an abstract syntax tree into a document.
|
||||||
#[derive(Debug, Clone)]
|
pub(crate) struct Engine<'a> {
|
||||||
pub struct Engine<'s> {
|
|
||||||
// Immutable
|
// Immutable
|
||||||
tree: &'s SyntaxTree<'s>,
|
tree: &'a SyntaxTree<'a>,
|
||||||
style: Style,
|
ctx: &'a Context<'a>,
|
||||||
|
|
||||||
// Mutable
|
// Mutable
|
||||||
fonts: Vec<Font>,
|
fonts: Vec<Font>,
|
||||||
@ -22,12 +23,12 @@ pub struct Engine<'s> {
|
|||||||
current_width: Size,
|
current_width: Size,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Engine<'s> {
|
impl<'a> Engine<'a> {
|
||||||
/// Create a new generator from a syntax tree.
|
/// 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 {
|
Engine {
|
||||||
style,
|
|
||||||
tree,
|
tree,
|
||||||
|
ctx: context,
|
||||||
fonts: Vec::new(),
|
fonts: Vec::new(),
|
||||||
active_font: 0,
|
active_font: 0,
|
||||||
text_commands: Vec::new(),
|
text_commands: Vec::new(),
|
||||||
@ -39,21 +40,33 @@ impl<'s> Engine<'s> {
|
|||||||
/// Generate the abstract document.
|
/// Generate the abstract document.
|
||||||
pub fn typeset(mut self) -> TypeResult<Document> {
|
pub fn typeset(mut self) -> TypeResult<Document> {
|
||||||
// Load font defined by style
|
// Load font defined by style
|
||||||
let font_family = self.style.font_families.first().unwrap();
|
let mut font = None;
|
||||||
let program = std::fs::read(format!("../fonts/{}-Regular.ttf", font_family)).unwrap();
|
let config = FontConfig::new(self.ctx.style.font_families.clone());
|
||||||
let font = Font::new(program).unwrap();
|
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.fonts.push(font);
|
||||||
self.active_font = 0;
|
self.active_font = 0;
|
||||||
|
|
||||||
// Move cursor to top-left position
|
// Move cursor to top-left position
|
||||||
self.text_commands.push(TextCommand::Move(
|
self.text_commands.push(TextCommand::Move(
|
||||||
self.style.margin_left,
|
self.ctx.style.margin_left,
|
||||||
self.style.height - self.style.margin_top
|
self.ctx.style.height - self.ctx.style.margin_top
|
||||||
));
|
));
|
||||||
|
|
||||||
// Set the current font
|
// 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.
|
// Iterate through the documents nodes.
|
||||||
for node in &self.tree.nodes {
|
for node in &self.tree.nodes {
|
||||||
@ -70,8 +83,8 @@ impl<'s> Engine<'s> {
|
|||||||
|
|
||||||
// Create a page from the contents.
|
// Create a page from the contents.
|
||||||
let page = Page {
|
let page = Page {
|
||||||
width: self.style.width,
|
width: self.ctx.style.width,
|
||||||
height: self.style.height,
|
height: self.ctx.style.height,
|
||||||
text: vec![Text {
|
text: vec![Text {
|
||||||
commands: self.text_commands,
|
commands: self.text_commands,
|
||||||
}],
|
}],
|
||||||
@ -88,8 +101,8 @@ impl<'s> Engine<'s> {
|
|||||||
|
|
||||||
let width = self.width(word);
|
let width = self.width(word);
|
||||||
if self.would_overflow(width) {
|
if self.would_overflow(width) {
|
||||||
let vertical_move = - self.style.font_size
|
let vertical_move = - self.ctx.style.font_size
|
||||||
* self.style.line_spacing
|
* self.ctx.style.line_spacing
|
||||||
* font.metrics.ascender;
|
* font.metrics.ascender;
|
||||||
self.text_commands.push(TextCommand::Move(Size::zero(), vertical_move));
|
self.text_commands.push(TextCommand::Move(Size::zero(), vertical_move));
|
||||||
|
|
||||||
@ -115,31 +128,51 @@ impl<'s> Engine<'s> {
|
|||||||
fn width(&self, word: &str) -> Size {
|
fn width(&self, word: &str) -> Size {
|
||||||
let font = &self.fonts[self.active_font];
|
let font = &self.fonts[self.active_font];
|
||||||
word.chars()
|
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()
|
.sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn would_overflow(&self, width: Size) -> bool {
|
fn would_overflow(&self, width: Size) -> bool {
|
||||||
let max_width = self.style.width
|
let max_width = self.ctx.style.width
|
||||||
- self.style.margin_left
|
- self.ctx.style.margin_left
|
||||||
- self.style.margin_right;
|
- self.ctx.style.margin_right;
|
||||||
|
|
||||||
self.current_width + width > max_width
|
self.current_width + width > max_width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result type used for parsing.
|
/// Result type used for typesetting.
|
||||||
type TypeResult<T> = std::result::Result<T, TypesetError>;
|
type TypeResult<T> = std::result::Result<T, TypesetError>;
|
||||||
|
|
||||||
/// The error type for typesetting.
|
/// 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 {
|
impl fmt::Display for TypesetError {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
Ok(())
|
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)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
134
src/font.rs
134
src/font.rs
@ -1,14 +1,17 @@
|
|||||||
//! Font loading, utility and subsetting.
|
//! Font loading, utility and subsetting.
|
||||||
|
|
||||||
|
#![macro_use]
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::fmt;
|
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 byteorder::{BE, ReadBytesExt, WriteBytesExt};
|
||||||
use opentype::{Error as OpentypeError, OpenTypeReader, Outlines, TableRecord, Tag};
|
use opentype::{Error as OpentypeError, OpenTypeReader, Outlines, TableRecord, Tag};
|
||||||
use opentype::tables::{Header, Name, CharMap, MaximumProfile, HorizontalMetrics, Post, OS2};
|
use opentype::tables::{Header, Name, CharMap, MaximumProfile, HorizontalMetrics, Post, OS2};
|
||||||
use opentype::tables::{MacStyleFlags, NameEntry};
|
use opentype::tables::{MacStyleFlags, NameEntry};
|
||||||
use crate::doc::Size;
|
use crate::doc::{Size, FontFamily};
|
||||||
|
|
||||||
|
|
||||||
/// An font wrapper which allows to subset a font.
|
/// 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>;
|
type FontResult<T> = Result<T, FontError>;
|
||||||
|
|
||||||
/// The error type for font operations.
|
/// The error type for font operations.
|
||||||
|
113
src/lib.rs
113
src/lib.rs
@ -1,24 +1,50 @@
|
|||||||
//! Typeset is a library for compiling documents written in the
|
//! The compiler for the _Typeset_ typesetting language 📜.
|
||||||
//! corresponding typesetting language into a typesetted document in an
|
//!
|
||||||
//! output format like _PDF_.
|
//! # 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
|
//! # 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.
|
//! // Simple example source code.
|
||||||
//! let src = "Hello World from Typeset!";
|
//! 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();
|
//! # let mut file = File::create("../target/typeset-hello.pdf").unwrap();
|
||||||
//!
|
//! compiler.write_pdf(source, &mut file).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();
|
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
pub mod syntax;
|
pub mod syntax;
|
||||||
@ -36,33 +62,51 @@ pub use crate::pdf::PdfError;
|
|||||||
use std::error;
|
use std::error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use crate::parsing::Parser;
|
|
||||||
use crate::syntax::SyntaxTree;
|
use crate::syntax::SyntaxTree;
|
||||||
use crate::engine::Engine;
|
use crate::parsing::Parser;
|
||||||
use crate::doc::{Document, Style};
|
use crate::doc::{Document, Style};
|
||||||
|
use crate::font::FontProvider;
|
||||||
|
use crate::engine::Engine;
|
||||||
use crate::pdf::PdfCreator;
|
use crate::pdf::PdfCreator;
|
||||||
|
|
||||||
|
|
||||||
/// Compiles source code into typesetted documents allowing to
|
/// Compiles source code into typesetted documents allowing to
|
||||||
/// retrieve results at various stages.
|
/// retrieve results at various stages.
|
||||||
pub struct Compiler {
|
pub struct Compiler<'p> {
|
||||||
/// Style for typesetting.
|
context: Context<'p>,
|
||||||
style: Style,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
/// Create a new compiler from a document.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new() -> Compiler {
|
pub fn new() -> Compiler<'p> {
|
||||||
Compiler {
|
Compiler {
|
||||||
|
context: Context {
|
||||||
style: Style::default(),
|
style: Style::default(),
|
||||||
|
font_providers: Vec::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the default style for typesetting.
|
/// Set the default style for typesetting.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn style(&mut self, style: Style) -> &mut Self {
|
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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +126,7 @@ impl Compiler {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn typeset(&self, source: &str) -> Result<Document, Error> {
|
pub fn typeset(&self, source: &str) -> Result<Document, Error> {
|
||||||
let tree = self.parse(source)?;
|
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.
|
/// Write the document as a _PDF_, returning how many bytes were written.
|
||||||
@ -156,13 +200,32 @@ impl From<PdfError> for Error {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use std::fs::File;
|
||||||
use crate::Compiler;
|
use crate::Compiler;
|
||||||
|
use crate::font::FileFontProvider;
|
||||||
|
|
||||||
/// Create a pdf with a name from the source code.
|
/// Create a pdf with a name from the source code.
|
||||||
fn test(name: &str, src: &str) {
|
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 path = format!("../target/typeset-pdf-{}.pdf", name);
|
||||||
let mut file = std::fs::File::create(path).unwrap();
|
let mut file = File::create(path).unwrap();
|
||||||
Compiler::new().write_pdf(src, &mut file).unwrap();
|
|
||||||
|
// Compile and output
|
||||||
|
compiler.write_pdf(src, &mut file).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -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>;
|
type PdfResult<T> = std::result::Result<T, PdfError>;
|
||||||
|
|
||||||
/// The error type for _PDF_ creation.
|
/// The error type for _PDF_ creation.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user