Restructure typeset crate ✈

This commit is contained in:
Laurenz 2019-03-11 22:15:34 +01:00
parent 0511979942
commit 107450ee5c
8 changed files with 362 additions and 322 deletions

View File

@ -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),
}

View File

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

View File

@ -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),
}
}
}

View File

@ -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.
");
}
}

View File

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

View File

@ -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;
/// 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();
/// The error type for _PDF_ creation.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct PdfWritingError {
/// A message describing the error.
message: String,
}
#[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 error::Error for PdfWritingError {}
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
View 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>>,
}

View File

@ -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();