From 10994ebac34d027fb1937f72183859142b1b6180 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 30 Mar 2019 16:01:45 +0100 Subject: [PATCH] =?UTF-8?q?Move=20exporting=20into=20seperate=20module=20?= =?UTF-8?q?=F0=9F=A7=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hello-typeset.pdf | Bin 0 -> 6748 bytes src/export/mod.rs | 3 + src/{ => export}/pdf.rs | 210 ++++++++++++++++++++-------------------- src/lib.rs | 175 +++++++++++++-------------------- 4 files changed, 177 insertions(+), 211 deletions(-) create mode 100644 hello-typeset.pdf create mode 100644 src/export/mod.rs rename src/{ => export}/pdf.rs (57%) diff --git a/hello-typeset.pdf b/hello-typeset.pdf new file mode 100644 index 0000000000000000000000000000000000000000..536dcd3a8749cafe7273926874894a4471326cba GIT binary patch literal 6748 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 PdfExporter { + PdfExporter {} + } + + /// Export a typesetted document into a writer. Returns how many bytes were written. + #[inline] + pub fn export(&self, document: &Document, target: W) -> PdfResult { + let mut engine = PdfEngine::new(document, target)?; + engine.write() + } +} + /// Writes documents in the _PDF_ format. -pub struct PdfCreator<'a, W: Write> { +#[derive(Debug)] +struct PdfEngine<'d, W: Write> { writer: PdfWriter, - doc: &'a Document, + doc: &'d Document, offsets: Offsets, fonts: Vec, } /// Offsets for the various groups of ids. +#[derive(Debug, Copy, Clone)] struct Offsets { catalog: Ref, page_tree: Ref, @@ -29,47 +51,42 @@ struct Offsets { fonts: (Ref, Ref), } -impl<'a, W: Write> PdfCreator<'a, W> { +impl<'d, W: Write> PdfEngine<'d, W> { /// Create a new _PDF_ Creator. - pub fn new(doc: &'a Document, target: W) -> PdfResult> { - // Calculate a unique id for all object to come + fn new(doc: &'d Document, target: W) -> PdfResult> { + // Calculate a unique id for all objects that will be written. let catalog = 1; let page_tree = catalog + 1; let pages = (page_tree + 1, page_tree + doc.pages.len() as Ref); let content_count = doc.pages.iter().flat_map(|p| p.text.iter()).count() as Ref; let contents = (pages.1 + 1, pages.1 + content_count); let fonts = (contents.1 + 1, contents.1 + 4 * doc.fonts.len() as Ref); + let offsets = Offsets { catalog, page_tree, pages, contents, fonts }; - let offsets = Offsets { - catalog, - page_tree, - pages, - contents, - fonts, - }; + // Create a subsetted PDF font for each font in the document. + let fonts = { + let mut font = 0usize; + let mut chars = vec![HashSet::new(); doc.fonts.len()]; - // Find out which chars are used in this document. - let mut char_sets = vec![HashSet::new(); doc.fonts.len()]; - let mut current_font: usize = 0; - for page in &doc.pages { - for text in &page.text { + // Iterate through every text object on every page and find out + // which characters they use. + for text in doc.pages.iter().flat_map(|page| page.text.iter()) { for command in &text.commands { match command { - TextCommand::Text(string) - => char_sets[current_font].extend(string.chars()), - TextCommand::SetFont(id, _) => current_font = *id, + TextCommand::Text(string) => chars[font].extend(string.chars()), + TextCommand::SetFont(id, _) => font = *id, _ => {}, } } } - } - // Create a subsetted pdf font. - let fonts = doc.fonts.iter().enumerate().map(|(i, font)| { - PdfFont::new(font, &char_sets[i]) - }).collect::>>()?; + doc.fonts.iter() + .enumerate() + .map(|(i, font)| PdfFont::new(font, &chars[i])) + .collect::>>()? + }; - Ok(PdfCreator { + Ok(PdfEngine { writer: PdfWriter::new(target), doc, offsets, @@ -78,58 +95,40 @@ impl<'a, W: Write> PdfCreator<'a, W> { } /// Write the complete document. - pub fn write(&mut self) -> PdfResult { - // Header + fn write(&mut self) -> PdfResult { + // Write all the things! self.writer.write_header(&Version::new(1, 7))?; - - // Document catalog, page tree and pages self.write_pages()?; - - // Contents self.write_contents()?; - - // Fonts self.write_fonts()?; - - // Cross-reference table self.writer.write_xref_table()?; - - // Trailer self.writer.write_trailer(&Trailer::new(self.offsets.catalog))?; - Ok(self.writer.written()) } - /// Write the document catalog, page tree and pages. + /// Write the document catalog and page tree. fn write_pages(&mut self) -> PdfResult<()> { - // The document catalog - self.writer.write_obj(self.offsets.catalog, - &Catalog::new(self.offsets.page_tree))?; + // The document catalog. + self.writer.write_obj(self.offsets.catalog, &Catalog::new(self.offsets.page_tree))?; - // Root page tree + // The root page tree. self.writer.write_obj(self.offsets.page_tree, PageTree::new() - .kids(self.offsets.pages.0 ..= self.offsets.pages.1) + .kids(ids(self.offsets.pages)) .resource(Resource::Font(1, self.offsets.fonts.0)) )?; - // The page objects - let mut id = self.offsets.pages.0; - for page in &self.doc.pages { + // The page objects. + for (id, page) in ids(self.offsets.pages).zip(&self.doc.pages) { self.writer.write_obj(id, Page::new(self.offsets.page_tree) - .media_box(Rect::new( - 0.0, 0.0, - page.width.to_points(), page.height.to_points()) - ) - .contents(self.offsets.contents.0 ..= self.offsets.contents.1) + .media_box(Rect::new(0.0, 0.0, page.width.to_points(), page.height.to_points())) + .contents(ids(self.offsets.contents)) )?; - - id += 1; } Ok(()) } - /// Write the page contents. + /// Write the contents of all pages. fn write_contents(&mut self) -> PdfResult<()> { let mut id = self.offsets.contents.0; for page in &self.doc.pages { @@ -141,40 +140,40 @@ impl<'a, W: Write> PdfCreator<'a, W> { Ok(()) } - fn write_text(&mut self, id: u32, text: &DocText) -> PdfResult<()> { - let mut object = Text::new(); - let mut current_font = 0; + /// Write one text object. + fn write_text(&mut self, id: u32, doc_text: &DocText) -> PdfResult<()> { + let mut font = 0; + let mut text = Text::new(); - for command in &text.commands { + for command in &doc_text.commands { match command { - TextCommand::Text(string) => { - let encoded = self.fonts[current_font].encode(&string); - object.tj(encoded); - }, + TextCommand::Text(string) => { text.tj(self.fonts[font].encode(&string)); }, + TextCommand::Move(x, y) => { text.td(x.to_points(), y.to_points()); }, TextCommand::SetFont(id, size) => { - current_font = *id; - object.tf(*id as u32 + 1, *size); + font = *id; + text.tf(*id as u32 + 1, *size); }, - TextCommand::Move(x, y) => { object.td(x.to_points(), y.to_points()); }, } } - self.writer.write_obj(id, &object.to_stream())?; + self.writer.write_obj(id, &text.to_stream())?; Ok(()) } - /// Write the fonts. + /// Write all the fonts. fn write_fonts(&mut self) -> PdfResult<()> { let mut id = self.offsets.fonts.0; for font in &self.fonts { + // Write the base font object referencing the CID font. self.writer.write_obj(id, &Type0Font::new( font.name.clone(), CMapEncoding::Predefined("Identity-H".to_owned()), id + 1 ))?; + // Write the CID font referencing the font descriptor. self.writer.write_obj(id + 1, CIDFont::new( CIDFontType::Type2, @@ -184,6 +183,7 @@ impl<'a, W: Write> PdfCreator<'a, W> { ).widths(vec![WidthRecord::start(0, font.widths.clone())]) )?; + // Write the font descriptor (contains the global information about the font). self.writer.write_obj(id + 2, FontDescriptor::new( font.name.clone(), @@ -198,6 +198,7 @@ impl<'a, W: Write> PdfCreator<'a, W> { .font_file_3(id + 3) )?; + // Finally write the subsetted font program. self.writer.write_obj(id + 3, &FontStream::new( &font.program, EmbeddedFontType::OpenType, @@ -210,7 +211,13 @@ impl<'a, W: Write> PdfCreator<'a, W> { } } +/// Create an iterator from reference pair. +fn ids((start, end): (Ref, Ref)) -> impl Iterator { + start ..= end +} + /// The data we need from the font. +#[derive(Debug, Clone)] struct PdfFont { font: Font, widths: Vec, @@ -226,7 +233,12 @@ struct PdfFont { impl PdfFont { /// Create a subetted version of the font and calculate some information /// needed for creating the _PDF_. - pub fn new(font: &Font, chars: &HashSet) -> PdfResult { + fn new(font: &Font, chars: &HashSet) -> PdfResult { + /// Convert a size into a _PDF_ glyph unit. + fn size_to_glyph_unit(size: Size) -> GlyphUnit { + (1000.0 * size.to_points()).round() as GlyphUnit + } + // Subset the font using the selected characters let subsetted = font.subsetted( chars.iter().cloned(), @@ -264,11 +276,6 @@ impl PdfFont { } } -/// Convert a size into a _PDF_ glyph unit. -fn size_to_glyph_unit(size: Size) -> GlyphUnit { - (1000.0 * size.to_points()).round() as GlyphUnit -} - impl std::ops::Deref for PdfFont { type Target = Font; @@ -278,53 +285,48 @@ impl std::ops::Deref for PdfFont { } /// Result type for _PDF_ creation. -type PdfResult = std::result::Result; +type PdfResult = std::result::Result; /// The error type for _PDF_ creation. -pub enum PdfError { +pub enum PdfExportError { /// An error occured while subsetting the font for the _PDF_. Font(FontError), /// An I/O Error on the underlying writable occured. Io(io::Error), } -impl error::Error for PdfError { - #[inline] - fn source(&self) -> Option<&(dyn error::Error + 'static)> { +impl std::error::Error for PdfExportError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - PdfError::Font(err) => Some(err), - PdfError::Io(err) => Some(err), + PdfExportError::Font(err) => Some(err), + PdfExportError::Io(err) => Some(err), } } } -impl fmt::Display for PdfError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl Display for PdfExportError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - PdfError::Font(err) => write!(f, "font error: {}", err), - PdfError::Io(err) => write!(f, "io error: {}", err), + PdfExportError::Font(err) => write!(f, "font error: {}", err), + PdfExportError::Io(err) => write!(f, "io error: {}", err), } } } -impl fmt::Debug for PdfError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self, f) +impl Debug for PdfExportError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) } } -impl From for PdfError { - #[inline] - fn from(err: io::Error) -> PdfError { - PdfError::Io(err) +impl From for PdfExportError { + fn from(err: io::Error) -> PdfExportError { + PdfExportError::Io(err) } } -impl From for PdfError { - #[inline] - fn from(err: FontError) -> PdfError { - PdfError::Font(err) +impl From for PdfExportError { + fn from(err: FontError) -> PdfExportError { + PdfExportError::Font(err) } } diff --git a/src/lib.rs b/src/lib.rs index 59a6a2794..18d165c8c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,76 +2,61 @@ //! //! # 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. +//! [iterator of tokens](crate::parsing::Tokens). Then the parser operates on that to construct +//! a syntax tree. The structures describing the tree can be found in the [`syntax`] module. +//! - **Typesetting:** The next step is to transform the syntax tree into a portable representation +//! of the typesetted document. Types for these can be found in the [`doc`] module. This +//! representation contains already the finished layout. +//! - **Exporting:** The finished document can then be exported into supported formats. Submodules +//! for the supported formats are located in the [`export`] module. Currently the only supported +//! format is _PDF_. //! //! # Example //! ``` //! use std::fs::File; -//! use typeset::{Compiler, font::FileFontProvider, file_font}; +//! use typeset::Compiler; +//! use typeset::{font::FileFontProvider, file_font}; +//! use typeset::export::pdf::PdfExporter; //! //! // Simple example source code. -//! let source = "Hello World from Typeset!"; +//! let src = "Hello World from Typeset!"; //! -//! // Create a compiler with a font provider that provides one font. +//! // Create a compiler with a font provider that provides three fonts +//! // (the default sans-serif fonts and a fallback for the emoji). //! let mut compiler = Compiler::new(); //! compiler.add_font_provider(FileFontProvider::new("../fonts", vec![ //! // 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. -//! # /* +//! // Compile the source code with the compiler. +//! let document = compiler.typeset(src).unwrap(); +//! +//! // Export the document into a PDF file. //! let mut file = File::create("hello-typeset.pdf").unwrap(); -//! # */ -//! # let mut file = File::create("../target/typeset-hello.pdf").unwrap(); -//! compiler.write_pdf(source, &mut file).unwrap(); +//! let exporter = PdfExporter::new(); +//! exporter.export(&document, &mut file).unwrap(); //! ``` -pub mod syntax; -pub mod doc; -pub mod font; -mod parsing; -mod engine; -mod pdf; -mod utility; - -pub use crate::parsing::{Tokens, ParseError}; -pub use crate::engine::TypesetError; -pub use crate::pdf::PdfError; - -use std::error; -use std::fmt; -use std::io::Write; +use std::fmt::{self, Display, Debug, Formatter}; use crate::syntax::SyntaxTree; -use crate::parsing::Parser; +use crate::parsing::{Tokens, Parser, ParseError}; use crate::doc::{Document, Style}; use crate::font::FontProvider; -use crate::engine::Engine; -use crate::pdf::PdfCreator; +use crate::engine::{Engine, TypesetError}; + +pub mod doc; +pub mod engine; +pub mod export; +pub mod font; +pub mod parsing; +pub mod syntax; +mod utility; -/// Compiles source code into typesetted documents allowing to -/// retrieve results at various stages. +/// Transforms source code into typesetted documents. +/// +/// Holds the compilation context, which can be configured through various methods. pub struct Compiler<'p> { context: Context<'p>, } @@ -83,8 +68,9 @@ struct Context<'p> { font_providers: Vec>, } +/// Functions to set up the compilation context. impl<'p> Compiler<'p> { - /// Create a new compiler from a document. + /// Create a new compiler. #[inline] pub fn new() -> Compiler<'p> { Compiler { @@ -95,44 +81,33 @@ impl<'p> Compiler<'p> { } } - /// Set the default style for typesetting. + /// Set the default style for the document. #[inline] - pub fn style(&mut self, style: Style) -> &mut Self { + pub fn set_style(&mut self, style: Style) { self.context.style = style; - self } - /// Add a font provider. + /// Add a font provider to the context of this compiler. #[inline] - pub fn add_font_provider(&mut self, provider: P) -> &mut Self - where P: FontProvider { + pub fn add_font_provider(&mut self, provider: P) where P: FontProvider { self.context.font_providers.push(Box::new(provider)); - self } +} - /// Return an iterator over the tokens of the document. +/// Compilation functions. +impl<'p> Compiler<'p> { + /// Parse source code into a syntax tree. #[inline] - pub fn tokenize<'s>(&self, source: &'s str) -> Tokens<'s> { - Tokens::new(source) + pub fn parse<'s>(&self, src: &'s str) -> Result, ParseError> { + Parser::new(Tokens::new(src)).parse() } - /// Return the abstract syntax tree representation of the document. + /// Compile a portable typesetted document from source code. #[inline] - pub fn parse<'s>(&self, source: &'s str) -> Result, ParseError> { - Parser::new(self.tokenize(source)).parse() - } - - /// Return the abstract typesetted representation of the document. - #[inline] - pub fn typeset(&self, source: &str) -> Result { - let tree = self.parse(source)?; - Engine::new(&tree, &self.context).typeset().map_err(Into::into) - } - - /// Write the document as a _PDF_, returning how many bytes were written. - pub fn write_pdf(&self, source: &str, target: &mut W) -> Result { - let document = self.typeset(source)?; - PdfCreator::new(&document, target)?.write().map_err(Into::into) + pub fn typeset(&self, src: &str) -> Result { + let tree = self.parse(src)?; + let engine = Engine::new(&tree, &self.context); + engine.typeset().map_err(Into::into) } } @@ -143,72 +118,57 @@ pub enum Error { 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_. - Pdf(PdfError), } -impl error::Error for Error { - #[inline] - fn source(&self) -> Option<&(dyn error::Error + 'static)> { +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Error::Parse(err) => Some(err), Error::Typeset(err) => Some(err), - Error::Pdf(err) => Some(err), } } } -impl fmt::Display for Error { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Error::Parse(err) => write!(f, "parse error: {}", err), Error::Typeset(err) => write!(f, "typeset error: {}", err), - Error::Pdf(err) => write!(f, "pdf error: {}", err), } } } -impl fmt::Debug for Error { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self, f) +impl Debug for Error { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) } } impl From for Error { - #[inline] fn from(err: ParseError) -> Error { Error::Parse(err) } } impl From for Error { - #[inline] fn from(err: TypesetError) -> Error { Error::Typeset(err) } } -impl From for Error { - #[inline] - fn from(err: PdfError) -> Error { - Error::Pdf(err) - } -} - #[cfg(test)] mod test { use std::fs::File; use crate::Compiler; + use crate::export::pdf::PdfExporter; 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![ + compiler.add_font_provider(FileFontProvider::new("../fonts", vec![ // Font family name, generic families, file, bold, italic file_font!("NotoSans", [SansSerif], "NotoSans-Regular.ttf", false, false), file_font!("NotoSans", [SansSerif], "NotoSans-Bold.ttf", true, false), @@ -217,15 +177,16 @@ mod test { 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; + // Compile into document + let document = compiler.typeset(src).unwrap(); + + // Write to file let path = format!("../target/typeset-pdf-{}.pdf", name); - let mut file = File::create(path).unwrap(); - - // Compile and output - compiler.write_pdf(src, &mut file).unwrap(); + let file = File::create(path).unwrap(); + let exporter = PdfExporter::new(); + exporter.export(&document, file).unwrap(); } #[test]