mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
Restructure typeset crate ✈
This commit is contained in:
parent
0511979942
commit
107450ee5c
22
src/doc.rs
22
src/doc.rs
@ -4,20 +4,26 @@ use std::ops;
|
||||
use crate::font::Font;
|
||||
|
||||
|
||||
/// A representation of a typesetted document.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Document {
|
||||
/// The pages of the document.
|
||||
pub pages: Vec<Page>,
|
||||
/// The fonts used in the document.
|
||||
pub fonts: Vec<Font>,
|
||||
}
|
||||
|
||||
/// Default styles for a document.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Style {
|
||||
// Paper
|
||||
/// The width and height of the paper.
|
||||
pub paper_size: [Size; 2],
|
||||
/// The [left, top, right, bottom] margins of the paper.
|
||||
pub margins: [Size; 4],
|
||||
|
||||
// Font handling
|
||||
/// A fallback list of font families to use.
|
||||
pub font_families: Vec<String>,
|
||||
/// The default font size.
|
||||
pub font_size: f32,
|
||||
}
|
||||
|
||||
@ -37,22 +43,30 @@ impl Default for Style {
|
||||
}
|
||||
}
|
||||
|
||||
/// A page with text contents in a document.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Page {
|
||||
pub width: Size,
|
||||
pub height: Size,
|
||||
/// The width and height of the page.
|
||||
pub size: [Size; 2],
|
||||
/// Text content on the page.
|
||||
pub text: Vec<Text>,
|
||||
}
|
||||
|
||||
/// A series of text command, that can be written on to a page.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Text {
|
||||
/// The text commands.
|
||||
pub commands: Vec<TextCommand>,
|
||||
}
|
||||
|
||||
/// Different commands for rendering text.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TextCommand {
|
||||
/// Writing of the text.
|
||||
Text(String),
|
||||
/// Moves from the *start* of the current line by an (x,y) offset.
|
||||
Move(Size, Size),
|
||||
/// Use the indexed font in the documents font list with a given font size.
|
||||
SetFont(usize, f32),
|
||||
}
|
||||
|
||||
|
@ -1,53 +1,26 @@
|
||||
//! Core typesetting engine.
|
||||
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use crate::parsing::{SyntaxTree, Node};
|
||||
use crate::syntax::{SyntaxTree, Node};
|
||||
use crate::doc::{Document, Style, Page, Text, TextCommand};
|
||||
use crate::font::Font;
|
||||
|
||||
|
||||
/// A type that can be typesetted into a document.
|
||||
pub trait Typeset {
|
||||
/// Generate a document from self.
|
||||
fn typeset(self) -> TypeResult<Document>;
|
||||
}
|
||||
|
||||
impl Typeset for SyntaxTree<'_> {
|
||||
fn typeset(self) -> TypeResult<Document> {
|
||||
Engine::new(self).typeset()
|
||||
}
|
||||
}
|
||||
|
||||
/// Result type used for parsing.
|
||||
type TypeResult<T> = std::result::Result<T, TypesetError>;
|
||||
|
||||
/// Errors occuring in the process of typesetting.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct TypesetError {
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for TypesetError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(&self.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Transforms an abstract syntax tree into a document.
|
||||
/// The core typesetting engine, transforming an abstract syntax tree into a document.
|
||||
#[derive(Debug, Clone)]
|
||||
struct Engine<'s> {
|
||||
pub struct Engine<'s> {
|
||||
tree: SyntaxTree<'s>,
|
||||
}
|
||||
|
||||
impl<'s> Engine<'s> {
|
||||
/// Create a new generator from a syntax tree.
|
||||
fn new(tree: SyntaxTree<'s>) -> Engine<'s> {
|
||||
pub fn new(tree: SyntaxTree<'s>) -> Engine<'s> {
|
||||
Engine { tree }
|
||||
}
|
||||
|
||||
/// Generate the abstract document.
|
||||
fn typeset(&mut self) -> TypeResult<Document> {
|
||||
pub fn typeset(&mut self) -> TypeResult<Document> {
|
||||
let style = Style::default();
|
||||
|
||||
// Load font defined by style
|
||||
@ -68,8 +41,7 @@ impl<'s> Engine<'s> {
|
||||
}
|
||||
|
||||
let page = Page {
|
||||
width: style.paper_size[0],
|
||||
height: style.paper_size[1],
|
||||
size: style.paper_size,
|
||||
text: vec![Text {
|
||||
commands: vec![
|
||||
TextCommand::Move(style.margins[0], style.paper_size[1] - style.margins[1]),
|
||||
@ -85,3 +57,20 @@ impl<'s> Engine<'s> {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Result type used for parsing.
|
||||
type TypeResult<T> = std::result::Result<T, TypesetError>;
|
||||
|
||||
/// The error type for typesetting.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct TypesetError {
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl error::Error for TypesetError {}
|
||||
|
||||
impl fmt::Display for TypesetError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(&self.message)
|
||||
}
|
||||
}
|
||||
|
80
src/font.rs
80
src/font.rs
@ -1,4 +1,4 @@
|
||||
//! Font utility and subsetting.
|
||||
//! Font loading, utility and subsetting.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::error;
|
||||
@ -13,10 +13,15 @@ use crate::doc::Size;
|
||||
/// An font wrapper which allows to subset a font.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Font {
|
||||
/// The base name of the font.
|
||||
pub name: String,
|
||||
/// The raw bytes of the font program.
|
||||
pub program: Vec<u8>,
|
||||
/// A 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 fallback glyph.
|
||||
pub default_glyph: u16,
|
||||
}
|
||||
|
||||
@ -54,7 +59,7 @@ impl Font {
|
||||
self.mapping.get(&c).map(|&g| g).unwrap_or(self.default_glyph)
|
||||
}
|
||||
|
||||
/// Encode the given text for our font (into glyph ids).
|
||||
/// Encode the given text for this font (into glyph ids).
|
||||
#[inline]
|
||||
pub fn encode(&self, text: &str) -> Vec<u8> {
|
||||
println!("encoding {} with {:?}", text, self.mapping);
|
||||
@ -69,11 +74,9 @@ impl Font {
|
||||
/// Generate a subsetted version of this font including only the chars listed in
|
||||
/// `chars`.
|
||||
///
|
||||
/// The resulting pair contains the new font data and the new glyph mapping.
|
||||
///
|
||||
/// 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.
|
||||
/// in the source font. All other tables will be dropped.
|
||||
pub fn subsetted<C, I1, S1, I2, S2>(
|
||||
&self,
|
||||
chars: C,
|
||||
@ -109,6 +112,7 @@ impl Font {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Subsetter<'p> {
|
||||
// Original font
|
||||
font: &'p Font,
|
||||
@ -128,7 +132,7 @@ struct Subsetter<'p> {
|
||||
|
||||
impl<'p> Subsetter<'p> {
|
||||
fn subset<I1, S1, I2, S2>(mut self, needed_tables: I1, optional_tables: I2)
|
||||
-> SubsettingResult<Font>
|
||||
-> SubsetResult<Font>
|
||||
where
|
||||
I1: IntoIterator<Item=S1>, S1: AsRef<str>,
|
||||
I2: IntoIterator<Item=S2>, S2: AsRef<str>
|
||||
@ -166,7 +170,7 @@ impl<'p> Subsetter<'p> {
|
||||
let widths = self.glyphs.iter()
|
||||
.map(|&glyph| self.font.widths.get(glyph as usize).map(|&w| w)
|
||||
.take_invalid("missing glyph metrics"))
|
||||
.collect::<SubsettingResult<Vec<_>>>()?;
|
||||
.collect::<SubsetResult<Vec<_>>>()?;
|
||||
|
||||
let mapping = self.chars.into_iter().enumerate().map(|(i, c)| (c, i as u16))
|
||||
.collect::<HashMap<char, u16>>();
|
||||
@ -180,7 +184,7 @@ impl<'p> Subsetter<'p> {
|
||||
})
|
||||
}
|
||||
|
||||
fn build_glyphs(&mut self) -> SubsettingResult<()> {
|
||||
fn build_glyphs(&mut self) -> SubsetResult<()> {
|
||||
self.read_cmap()?;
|
||||
let cmap = self.cmap.as_ref().unwrap();
|
||||
|
||||
@ -240,7 +244,7 @@ impl<'p> Subsetter<'p> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_header(&mut self) -> SubsettingResult<()> {
|
||||
fn write_header(&mut self) -> SubsetResult<()> {
|
||||
// Create an output buffer
|
||||
let header_len = 12 + self.records.len() * 16;
|
||||
let mut header = Vec::with_capacity(header_len);
|
||||
@ -282,7 +286,7 @@ impl<'p> Subsetter<'p> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_table(&mut self, tag: Tag) -> SubsettingResult<()> {
|
||||
fn write_table(&mut self, tag: Tag) -> SubsetResult<()> {
|
||||
match tag.value() {
|
||||
b"head" | b"cvt " | b"prep" | b"fpgm" | b"name" | b"post" | b"OS/2" => {
|
||||
self.copy_table(tag)
|
||||
@ -428,15 +432,15 @@ impl<'p> Subsetter<'p> {
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_table(&mut self, tag: Tag) -> SubsettingResult<()> {
|
||||
fn copy_table(&mut self, tag: Tag) -> SubsetResult<()> {
|
||||
self.write_table_body(tag, |this| {
|
||||
let table = this.get_table_data(tag)?;
|
||||
Ok(this.body.extend(table))
|
||||
})
|
||||
}
|
||||
|
||||
fn write_table_body<F>(&mut self, tag: Tag, writer: F) -> SubsettingResult<()>
|
||||
where F: FnOnce(&mut Self) -> SubsettingResult<()> {
|
||||
fn write_table_body<F>(&mut self, tag: Tag, writer: F) -> SubsetResult<()>
|
||||
where F: FnOnce(&mut Self) -> SubsetResult<()> {
|
||||
let start = self.body.len();
|
||||
writer(self)?;
|
||||
let end = self.body.len();
|
||||
@ -452,8 +456,7 @@ impl<'p> Subsetter<'p> {
|
||||
}))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_table_data(&self, tag: Tag) -> SubsettingResult<&'p [u8]> {
|
||||
fn get_table_data(&self, tag: Tag) -> SubsetResult<&'p [u8]> {
|
||||
let record = match self.tables.binary_search_by_key(&tag, |r| r.tag) {
|
||||
Ok(index) => &self.tables[index],
|
||||
Err(_) => return Err(SubsettingError::MissingTable(tag.to_string())),
|
||||
@ -464,26 +467,23 @@ impl<'p> Subsetter<'p> {
|
||||
.take_bytes()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn contains(&self, tag: Tag) -> bool {
|
||||
self.tables.binary_search_by_key(&tag, |r| r.tag).is_ok()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read_cmap(&mut self) -> SubsettingResult<()> {
|
||||
fn read_cmap(&mut self) -> SubsetResult<()> {
|
||||
Ok(if self.cmap.is_none() {
|
||||
self.cmap = Some(self.reader.read_table::<CharMap>()?);
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read_hmtx(&mut self) -> SubsettingResult<()> {
|
||||
fn read_hmtx(&mut self) -> SubsetResult<()> {
|
||||
Ok(if self.hmtx.is_none() {
|
||||
self.hmtx = Some(self.reader.read_table::<HorizontalMetrics>()?);
|
||||
})
|
||||
}
|
||||
|
||||
fn read_loca(&mut self) -> SubsettingResult<()> {
|
||||
fn read_loca(&mut self) -> SubsetResult<()> {
|
||||
Ok(if self.loca.is_none() {
|
||||
let mut table = self.get_table_data("loca".parse().unwrap())?;
|
||||
let format = self.reader.read_table::<Header>()?.index_to_loc_format;
|
||||
@ -505,7 +505,6 @@ impl<'p> Subsetter<'p> {
|
||||
|
||||
/// Calculate a checksum over the sliced data as sum of u32's.
|
||||
/// The data length has to be a multiple of four.
|
||||
#[inline]
|
||||
fn calculate_check_sum(data: &[u8]) -> u32 {
|
||||
let mut sum = 0u32;
|
||||
data.chunks_exact(4).for_each(|c| {
|
||||
@ -522,26 +521,24 @@ fn calculate_check_sum(data: &[u8]) -> u32 {
|
||||
trait TakeInvalid<T>: Sized {
|
||||
/// Pull the type out of the option, returning a subsetting error
|
||||
/// about an invalid font wrong.
|
||||
fn take_invalid<S: Into<String>>(self, message: S) -> SubsettingResult<T>;
|
||||
fn take_invalid<S: Into<String>>(self, message: S) -> SubsetResult<T>;
|
||||
|
||||
/// Pull the type out of the option, returning an error about missing
|
||||
/// bytes if it is nothing.
|
||||
#[inline]
|
||||
fn take_bytes(self) -> SubsettingResult<T> {
|
||||
fn take_bytes(self) -> SubsetResult<T> {
|
||||
self.take_invalid("expected more bytes")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TakeInvalid<T> for Option<T> {
|
||||
#[inline]
|
||||
fn take_invalid<S: Into<String>>(self, message: S) -> SubsettingResult<T> {
|
||||
fn take_invalid<S: Into<String>>(self, message: S) -> SubsetResult<T> {
|
||||
self.ok_or(SubsettingError::Opentype(opentype::Error::InvalidFont(message.into())))
|
||||
}
|
||||
}
|
||||
|
||||
type SubsettingResult<T> = Result<T, SubsettingError>;
|
||||
type SubsetResult<T> = Result<T, SubsettingError>;
|
||||
|
||||
/// A failure when subsetting a font.
|
||||
/// The error type for font subsetting.
|
||||
#[derive(Debug)]
|
||||
pub enum SubsettingError {
|
||||
/// A requested table was not present in the source font.
|
||||
@ -564,6 +561,19 @@ impl error::Error for SubsettingError {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SubsettingError {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use SubsettingError::*;
|
||||
match self {
|
||||
MissingTable(table) => write!(f, "missing table: {}", table),
|
||||
UnsupportedTable(table) => write!(f, "unsupported table: {}", table),
|
||||
MissingCharacter(c) => write!(f, "missing character: {}", c),
|
||||
Opentype(err) => fmt::Display::fmt(err, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for SubsettingError {
|
||||
#[inline]
|
||||
fn from(err: io::Error) -> SubsettingError {
|
||||
@ -580,15 +590,3 @@ impl From<opentype::Error> for SubsettingError {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SubsettingError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use SubsettingError::*;
|
||||
match self {
|
||||
MissingTable(table) => write!(f, "missing table: {}", table),
|
||||
UnsupportedTable(table) => write!(f, "unsupported table: {}", table),
|
||||
MissingCharacter(c) => write!(f, "missing character: {}", c),
|
||||
Opentype(err) => fmt::Display::fmt(err, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
151
src/lib.rs
151
src/lib.rs
@ -5,7 +5,7 @@
|
||||
//! # Example
|
||||
//! This is an example of compiling a really simple document into _PDF_.
|
||||
//! ```
|
||||
//! use typeset::{parsing::ParseTree, engine::Typeset, write::WritePdf};
|
||||
//! use typeset::Compiler;
|
||||
//!
|
||||
//! // Create an output file.
|
||||
//! # /*
|
||||
@ -13,22 +13,147 @@
|
||||
//! # */
|
||||
//! # let mut file = std::fs::File::create("../target/typeset-hello.pdf").unwrap();
|
||||
//!
|
||||
//! // Parse the source and then generate the document.
|
||||
//! let src = "Hello World from Typeset‼";
|
||||
//! let doc = src.parse_tree().unwrap().typeset().unwrap();
|
||||
//! // Create a compiler and export a PDF.
|
||||
//! let src = "Hello World from Typeset!";
|
||||
//! let compiler = Compiler::new(src);
|
||||
//!
|
||||
//! // Write the document into file as PDF.
|
||||
//! file.write_pdf(&doc).unwrap();
|
||||
//! // Write the document into a file as a PDF.
|
||||
//! compiler.write_pdf(&mut file).unwrap();
|
||||
//! ```
|
||||
|
||||
pub mod syntax;
|
||||
pub mod doc;
|
||||
pub mod font;
|
||||
mod parsing;
|
||||
mod engine;
|
||||
mod pdf;
|
||||
mod utility;
|
||||
pub mod parsing;
|
||||
pub mod doc;
|
||||
pub mod engine;
|
||||
pub mod font;
|
||||
|
||||
/// Writing of documents into supported formats.
|
||||
pub mod write {
|
||||
pub use crate::pdf::{WritePdf, PdfWritingError};
|
||||
pub use crate::parsing::{Tokens, Parser, ParseError};
|
||||
pub use crate::engine::{Engine, TypesetError};
|
||||
pub use crate::pdf::{PdfCreator, PdfWritingError};
|
||||
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::io::Write;
|
||||
use crate::syntax::SyntaxTree;
|
||||
use crate::doc::Document;
|
||||
|
||||
|
||||
/// Emits various compiled intermediates from source code.
|
||||
pub struct Compiler<'s> {
|
||||
/// The source code of the document.
|
||||
source: &'s str,
|
||||
}
|
||||
|
||||
impl<'s> Compiler<'s> {
|
||||
/// Create a new compiler from a document.
|
||||
#[inline]
|
||||
pub fn new(source: &'s str) -> Compiler<'s> {
|
||||
Compiler { source }
|
||||
}
|
||||
|
||||
/// Return an iterator over the tokens of the document.
|
||||
#[inline]
|
||||
pub fn tokenize(&self) -> Tokens<'s> {
|
||||
Tokens::new(self.source)
|
||||
}
|
||||
|
||||
/// Return the abstract syntax tree representation of the document.
|
||||
#[inline]
|
||||
pub fn parse(&self) -> Result<SyntaxTree<'s>, Error> {
|
||||
Parser::new(self.tokenize()).parse().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Return the abstract typesetted representation of the document.
|
||||
#[inline]
|
||||
pub fn typeset(&self) -> Result<Document, Error> {
|
||||
Engine::new(self.parse()?).typeset().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Write the document as a _PDF_, returning how many bytes were written.
|
||||
pub fn write_pdf<W: Write>(&self, target: &mut W) -> Result<usize, Error> {
|
||||
PdfCreator::new(target, &self.typeset()?)?.write().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
/// The error type for compilation.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Error {
|
||||
/// An error that occured while transforming source code into
|
||||
/// an abstract syntax tree.
|
||||
Parse(ParseError),
|
||||
/// An error that occured while typesetting into an abstract document.
|
||||
Typeset(TypesetError),
|
||||
/// An error that occured while writing the document as a _PDF_.
|
||||
PdfWrite(PdfWritingError)
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
#[inline]
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
match self {
|
||||
Error::Parse(err) => Some(err),
|
||||
Error::Typeset(err) => Some(err),
|
||||
Error::PdfWrite(err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Error::Parse(err) => write!(f, "parse error: {}", err),
|
||||
Error::Typeset(err) => write!(f, "typeset error: {}", err),
|
||||
Error::PdfWrite(err) => write!(f, "typeset error: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError> for Error {
|
||||
#[inline]
|
||||
fn from(err: ParseError) -> Error {
|
||||
Error::Parse(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TypesetError> for Error {
|
||||
#[inline]
|
||||
fn from(err: TypesetError) -> Error {
|
||||
Error::Typeset(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PdfWritingError> for Error {
|
||||
#[inline]
|
||||
fn from(err: PdfWritingError) -> Error {
|
||||
Error::PdfWrite(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::Compiler;
|
||||
|
||||
/// Create a pdf with a name from the source code.
|
||||
fn test(name: &str, src: &str) {
|
||||
let path = format!("../target/typeset-pdf-{}.pdf", name);
|
||||
let mut file = std::fs::File::create(path).unwrap();
|
||||
Compiler::new(src).write_pdf(&mut file).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pdf() {
|
||||
test("unicode", "∑mbe∂∂ed font with Unicode!");
|
||||
test("parentheses", "Text with ) and ( or (enclosed) works.");
|
||||
test("composite-glyph", "Composite character‼");
|
||||
test("multiline","
|
||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||||
diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed
|
||||
diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
|
||||
Stet clita kasd gubergren, no sea takimata sanctus est.
|
||||
");
|
||||
}
|
||||
}
|
||||
|
166
src/parsing.rs
166
src/parsing.rs
@ -1,56 +1,14 @@
|
||||
//! Parsing of source code into tokens and syntax trees.
|
||||
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::iter::Peekable;
|
||||
use std::mem::swap;
|
||||
use unicode_segmentation::{UnicodeSegmentation, UWordBounds};
|
||||
use crate::syntax::*;
|
||||
use crate::utility::{Splinor, Spline, Splined, StrExt};
|
||||
|
||||
|
||||
/// A logical unit of the incoming text stream.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Token<'s> {
|
||||
/// One or more whitespace (non-newline) codepoints.
|
||||
Space,
|
||||
/// A line feed (either `\n` or `\r\n`).
|
||||
Newline,
|
||||
/// A left bracket: `[`.
|
||||
LeftBracket,
|
||||
/// A right bracket: `]`.
|
||||
RightBracket,
|
||||
/// A colon (`:`) indicating the beginning of function arguments.
|
||||
///
|
||||
/// If a colon occurs outside of the function header, it will be
|
||||
/// tokenized as a [Word](Token::Word).
|
||||
Colon,
|
||||
/// Same as with [Colon](Token::Colon).
|
||||
Equals,
|
||||
/// Two underscores, indicating text in _italics_.
|
||||
DoubleUnderscore,
|
||||
/// Two stars, indicating **bold** text.
|
||||
DoubleStar,
|
||||
/// A dollar sign, indicating _mathematical_ content.
|
||||
Dollar,
|
||||
/// A hashtag starting a _comment_.
|
||||
Hashtag,
|
||||
/// Everything else just is a literal word.
|
||||
Word(&'s str),
|
||||
}
|
||||
|
||||
|
||||
/// A type that is separable into logical units (tokens).
|
||||
pub trait Tokenize {
|
||||
/// Tokenize self into logical units.
|
||||
fn tokenize<'s>(&'s self) -> Tokens<'s>;
|
||||
}
|
||||
|
||||
impl Tokenize for str {
|
||||
fn tokenize<'s>(&'s self) -> Tokens<'s> {
|
||||
Tokens::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// An iterator over the tokens of a text.
|
||||
#[derive(Clone)]
|
||||
pub struct Tokens<'s> {
|
||||
@ -253,99 +211,9 @@ impl<'s> Tokens<'s> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A tree representation of the source.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SyntaxTree<'s> {
|
||||
/// The children.
|
||||
pub nodes: Vec<Node<'s>>,
|
||||
}
|
||||
|
||||
impl<'s> SyntaxTree<'s> {
|
||||
/// Create an empty syntax tree.
|
||||
#[inline]
|
||||
pub fn new() -> SyntaxTree<'s> {
|
||||
SyntaxTree { nodes: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
/// A node in the abstract syntax tree.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Node<'s> {
|
||||
/// Whitespace between other nodes.
|
||||
Space,
|
||||
/// A line feed.
|
||||
Newline,
|
||||
/// Indicates that italics were enabled/disabled.
|
||||
ToggleItalics,
|
||||
/// Indicates that boldface was enabled/disabled.
|
||||
ToggleBold,
|
||||
/// Indicates that math mode was enabled/disabled.
|
||||
ToggleMath,
|
||||
/// A literal word.
|
||||
Word(&'s str),
|
||||
/// A function invocation.
|
||||
Func(Function<'s>),
|
||||
}
|
||||
|
||||
/// A node representing a function invocation.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Function<'s> {
|
||||
/// The name of the function.
|
||||
pub name: &'s str,
|
||||
/// Some syntax tree if the function had a body (second set of brackets),
|
||||
/// otherwise nothing.
|
||||
pub body: Option<SyntaxTree<'s>>,
|
||||
}
|
||||
|
||||
|
||||
/// A type that is parsable into a syntax tree.
|
||||
pub trait ParseTree<'s> {
|
||||
/// Parse self into a syntax tree.
|
||||
fn parse_tree(self) -> ParseResult<SyntaxTree<'s>>;
|
||||
}
|
||||
|
||||
impl<'s> ParseTree<'s> for &'s str {
|
||||
#[inline]
|
||||
fn parse_tree(self) -> ParseResult<SyntaxTree<'s>> {
|
||||
self.tokenize().parse_tree()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ParseTree<'s> for Tokens<'s> {
|
||||
#[inline]
|
||||
fn parse_tree(self) -> ParseResult<SyntaxTree<'s>> {
|
||||
Parser::new(self).parse()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ParseTree<'s> for Vec<Token<'s>> {
|
||||
#[inline]
|
||||
fn parse_tree(self) -> ParseResult<SyntaxTree<'s>> {
|
||||
Parser::new(self.into_iter()).parse()
|
||||
}
|
||||
}
|
||||
|
||||
/// Result type used for parsing.
|
||||
type ParseResult<T> = std::result::Result<T, ParseError>;
|
||||
|
||||
/// A failure when parsing.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct ParseError {
|
||||
/// A message describing the error.
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(&self.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Parses a token stream into an abstract syntax tree.
|
||||
#[derive(Debug, Clone)]
|
||||
struct Parser<'s, T> where T: Iterator<Item = Token<'s>> {
|
||||
pub struct Parser<'s, T> where T: Iterator<Item = Token<'s>> {
|
||||
tokens: Peekable<T>,
|
||||
state: ParserState,
|
||||
stack: Vec<Function<'s>>,
|
||||
@ -363,7 +231,7 @@ enum ParserState {
|
||||
|
||||
impl<'s, T> Parser<'s, T> where T: Iterator<Item = Token<'s>> {
|
||||
/// Create a new parser from a type that emits results of tokens.
|
||||
fn new(tokens: T) -> Parser<'s, T> {
|
||||
pub fn new(tokens: T) -> Parser<'s, T> {
|
||||
Parser {
|
||||
tokens: tokens.peekable(),
|
||||
state: ParserState::Body,
|
||||
@ -373,7 +241,7 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item = Token<'s>> {
|
||||
}
|
||||
|
||||
/// Parse into an abstract syntax tree.
|
||||
fn parse(mut self) -> ParseResult<SyntaxTree<'s>> {
|
||||
pub fn parse(mut self) -> ParseResult<SyntaxTree<'s>> {
|
||||
use ParserState as PS;
|
||||
|
||||
while let Some(token) = self.tokens.next() {
|
||||
@ -487,6 +355,24 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item = Token<'s>> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Result type used for parsing.
|
||||
type ParseResult<T> = std::result::Result<T, ParseError>;
|
||||
|
||||
/// The error type for parsing.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct ParseError {
|
||||
/// A message describing the error.
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl error::Error for ParseError {}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(&self.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod token_tests {
|
||||
@ -497,7 +383,7 @@ mod token_tests {
|
||||
|
||||
/// Test if the source code tokenizes to the tokens.
|
||||
fn test(src: &str, tokens: Vec<Token>) {
|
||||
assert_eq!(src.tokenize().collect::<Vec<_>>(), tokens);
|
||||
assert_eq!(Tokens::new(src).collect::<Vec<_>>(), tokens);
|
||||
}
|
||||
|
||||
/// Tokenizes the basic building blocks.
|
||||
@ -605,12 +491,12 @@ mod parse_tests {
|
||||
|
||||
/// Test if the source code parses into the syntax tree.
|
||||
fn test(src: &str, tree: SyntaxTree) {
|
||||
assert_eq!(src.parse_tree(), Ok(tree));
|
||||
assert_eq!(Parser::new(Tokens::new(src)).parse(), Ok(tree));
|
||||
}
|
||||
|
||||
/// Test if the source parses into the error.
|
||||
fn test_err(src: &str, err: ParseError) {
|
||||
assert_eq!(src.parse_tree(), Err(err));
|
||||
assert_eq!(Parser::new(Tokens::new(src)).parse(), Err(err));
|
||||
}
|
||||
|
||||
/// Short cut macro to create a syntax tree.
|
||||
|
125
src/pdf.rs
125
src/pdf.rs
@ -1,6 +1,7 @@
|
||||
//! Writing of documents in the _PDF_ format.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::io::{self, Write, Cursor};
|
||||
use pdf::{PdfWriter, Reference, Rect, Version, Trailer};
|
||||
@ -12,61 +13,8 @@ use crate::doc::{self, Document, TextCommand};
|
||||
use crate::font::Font;
|
||||
|
||||
|
||||
/// A type that is a sink for documents that can be written in the _PDF_ format.
|
||||
pub trait WritePdf {
|
||||
/// Write a document into self, returning how many bytes were written.
|
||||
fn write_pdf(&mut self, doc: &Document) -> PdfResult<usize>;
|
||||
}
|
||||
|
||||
impl<W: Write> WritePdf for W {
|
||||
#[inline]
|
||||
fn write_pdf(&mut self, doc: &Document) -> PdfResult<usize> {
|
||||
PdfCreator::new(self, doc)?.write()
|
||||
}
|
||||
}
|
||||
|
||||
/// Result type used for parsing.
|
||||
type PdfResult<T> = std::result::Result<T, PdfWritingError>;
|
||||
|
||||
/// A failure while writing a _PDF_.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct PdfWritingError {
|
||||
/// A message describing the error.
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl From<io::Error> for PdfWritingError {
|
||||
#[inline]
|
||||
fn from(err: io::Error) -> PdfWritingError {
|
||||
PdfWritingError { message: format!("{}", err) }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<opentype::Error> for PdfWritingError {
|
||||
#[inline]
|
||||
fn from(err: opentype::Error) -> PdfWritingError {
|
||||
PdfWritingError { message: format!("{}", err) }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::font::SubsettingError> for PdfWritingError {
|
||||
#[inline]
|
||||
fn from(err: crate::font::SubsettingError) -> PdfWritingError {
|
||||
PdfWritingError { message: format!("{}", err) }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PdfWritingError {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(&self.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Keeps track of the document while letting the pdf writer
|
||||
/// generate the _PDF_.
|
||||
struct PdfCreator<'a, W: Write> {
|
||||
/// Writes documents in the _PDF_ format.
|
||||
pub struct PdfCreator<'a, W: Write> {
|
||||
writer: PdfWriter<'a, W>,
|
||||
doc: &'a Document,
|
||||
offsets: Offsets,
|
||||
@ -137,7 +85,7 @@ impl<'a, W: Write> PdfCreator<'a, W> {
|
||||
}
|
||||
|
||||
/// Write the complete document.
|
||||
fn write(&mut self) -> PdfResult<usize> {
|
||||
pub fn write(&mut self) -> PdfResult<usize> {
|
||||
// Header
|
||||
self.writer.write_header(&Version::new(1, 7))?;
|
||||
|
||||
@ -174,8 +122,8 @@ impl<'a, W: Write> PdfCreator<'a, W> {
|
||||
// The page objects
|
||||
let mut id = self.offsets.pages.0;
|
||||
for page in &self.doc.pages {
|
||||
let width = page.width.to_points();
|
||||
let height = page.height.to_points();
|
||||
let width = page.size[0].to_points();
|
||||
let height = page.size[1].to_points();
|
||||
|
||||
self.writer.write_obj(id, Page::new(self.offsets.page_tree)
|
||||
.media_box(Rect::new(0.0, 0.0, width, height))
|
||||
@ -274,7 +222,6 @@ impl<'a, W: Write> PdfCreator<'a, W> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The data we need from the font.
|
||||
struct PdfFont {
|
||||
font: Font,
|
||||
@ -302,7 +249,7 @@ impl PdfFont {
|
||||
let subsetted = font.subsetted(
|
||||
chars.iter().cloned(),
|
||||
&["head", "hhea", "maxp", "hmtx", "loca", "glyf"],
|
||||
&["cvt ", "prep", "fpgm", "OS/2", "cmap", "name", "post"],
|
||||
&["cvt ", "prep", "fpgm", /* "OS/2", "cmap", "name", "post" */],
|
||||
)?;
|
||||
|
||||
let mut flags = FontFlags::empty();
|
||||
@ -341,37 +288,47 @@ impl PdfFont {
|
||||
impl std::ops::Deref for PdfFont {
|
||||
type Target = Font;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Font {
|
||||
&self.font
|
||||
}
|
||||
}
|
||||
|
||||
/// Result type used for parsing.
|
||||
type PdfResult<T> = std::result::Result<T, PdfWritingError>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod pdf_tests {
|
||||
use super::*;
|
||||
use crate::parsing::ParseTree;
|
||||
use crate::engine::Typeset;
|
||||
/// The error type for _PDF_ creation.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct PdfWritingError {
|
||||
/// A message describing the error.
|
||||
message: String,
|
||||
}
|
||||
|
||||
/// Create a pdf with a name from the source code.
|
||||
fn test(name: &str, src: &str) {
|
||||
let doc = src.parse_tree().unwrap().typeset().unwrap();
|
||||
let path = format!("../target/typeset-pdf-{}.pdf", name);
|
||||
let mut file = std::fs::File::create(path).unwrap();
|
||||
file.write_pdf(&doc).unwrap();
|
||||
}
|
||||
impl error::Error for PdfWritingError {}
|
||||
|
||||
#[test]
|
||||
fn pdf() {
|
||||
test("unicode", "∑mbe∂∂ed font with Unicode!");
|
||||
test("parentheses", "Text with ) and ( or (enclosed) works.");
|
||||
test("composite-glyph", "Composite character‼");
|
||||
test("multiline","
|
||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
|
||||
diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed
|
||||
diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
|
||||
Stet clita kasd gubergren, no sea takimata sanctus est.
|
||||
");
|
||||
impl From<io::Error> for PdfWritingError {
|
||||
#[inline]
|
||||
fn from(err: io::Error) -> PdfWritingError {
|
||||
PdfWritingError { message: format!("{}", err) }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<opentype::Error> for PdfWritingError {
|
||||
#[inline]
|
||||
fn from(err: opentype::Error) -> PdfWritingError {
|
||||
PdfWritingError { message: format!("{}", err) }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::font::SubsettingError> for PdfWritingError {
|
||||
#[inline]
|
||||
fn from(err: crate::font::SubsettingError) -> PdfWritingError {
|
||||
PdfWritingError { message: format!("{}", err) }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PdfWritingError {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(&self.message)
|
||||
}
|
||||
}
|
||||
|
76
src/syntax.rs
Normal file
76
src/syntax.rs
Normal file
@ -0,0 +1,76 @@
|
||||
//! Token and abstract syntax tree representations.
|
||||
|
||||
|
||||
/// A logical unit of the incoming text stream.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Token<'s> {
|
||||
/// One or more whitespace (non-newline) codepoints.
|
||||
Space,
|
||||
/// A line feed (either `\n` or `\r\n`).
|
||||
Newline,
|
||||
/// A left bracket: `[`.
|
||||
LeftBracket,
|
||||
/// A right bracket: `]`.
|
||||
RightBracket,
|
||||
/// A colon (`:`) indicating the beginning of function arguments.
|
||||
///
|
||||
/// If a colon occurs outside of the function header, it will be
|
||||
/// tokenized as a [Word](Token::Word).
|
||||
Colon,
|
||||
/// Same as with [Colon](Token::Colon).
|
||||
Equals,
|
||||
/// Two underscores, indicating text in _italics_.
|
||||
DoubleUnderscore,
|
||||
/// Two stars, indicating **bold** text.
|
||||
DoubleStar,
|
||||
/// A dollar sign, indicating _mathematical_ content.
|
||||
Dollar,
|
||||
/// A hashtag starting a _comment_.
|
||||
Hashtag,
|
||||
/// Everything else just is a literal word.
|
||||
Word(&'s str),
|
||||
}
|
||||
|
||||
/// A tree representation of the source.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SyntaxTree<'s> {
|
||||
/// The children.
|
||||
pub nodes: Vec<Node<'s>>,
|
||||
}
|
||||
|
||||
impl<'s> SyntaxTree<'s> {
|
||||
/// Create an empty syntax tree.
|
||||
#[inline]
|
||||
pub fn new() -> SyntaxTree<'s> {
|
||||
SyntaxTree { nodes: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
/// A node in the abstract syntax tree.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Node<'s> {
|
||||
/// Whitespace between other nodes.
|
||||
Space,
|
||||
/// A line feed.
|
||||
Newline,
|
||||
/// Indicates that italics were enabled/disabled.
|
||||
ToggleItalics,
|
||||
/// Indicates that boldface was enabled/disabled.
|
||||
ToggleBold,
|
||||
/// Indicates that math mode was enabled/disabled.
|
||||
ToggleMath,
|
||||
/// A literal word.
|
||||
Word(&'s str),
|
||||
/// A function invocation.
|
||||
Func(Function<'s>),
|
||||
}
|
||||
|
||||
/// A node representing a function invocation.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Function<'s> {
|
||||
/// The name of the function.
|
||||
pub name: &'s str,
|
||||
/// Some syntax tree if the function had a body (second set of brackets),
|
||||
/// otherwise nothing.
|
||||
pub body: Option<SyntaxTree<'s>>,
|
||||
}
|
@ -29,7 +29,6 @@ pub trait Splinor {
|
||||
}
|
||||
|
||||
impl Splinor for str {
|
||||
#[inline]
|
||||
fn spline<'s, T: Clone>(&'s self, pat: &'s str, splinor: T) -> Spline<'s, T> {
|
||||
Spline {
|
||||
splinor: Splined::Splinor(splinor),
|
||||
@ -61,7 +60,6 @@ pub enum Splined<'s, T> {
|
||||
impl<'s, T: Clone> Iterator for Spline<'s, T> {
|
||||
type Item = Splined<'s, T>;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Splined<'s, T>> {
|
||||
if self.next_splinor && self.split.peek().is_some() {
|
||||
self.next_splinor = false;
|
||||
@ -73,7 +71,6 @@ impl<'s, T: Clone> Iterator for Spline<'s, T> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// More useful functions on `str`'s.
|
||||
pub trait StrExt {
|
||||
/// Whether self consists only of whitespace.
|
||||
@ -84,12 +81,10 @@ pub trait StrExt {
|
||||
}
|
||||
|
||||
impl StrExt for str {
|
||||
#[inline]
|
||||
fn is_whitespace(&self) -> bool {
|
||||
self.chars().all(|c| c.is_whitespace() && c != '\n')
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_identifier(&self) -> bool {
|
||||
let mut chars = self.chars();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user