From b96a7e0cf3c97463ecb746d859b675541a427774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurenz=20M=C3=A4dje?= Date: Sun, 28 Jul 2019 22:27:09 +0200 Subject: [PATCH] =?UTF-8?q?Reuse=20font=20loader=20across=20compilations?= =?UTF-8?q?=20=F0=9F=94=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- benches/complete.rs | 35 +++++++++++++++++++++++++--- benches/font.rs | 7 +++--- src/bin/main.rs | 2 +- src/doc.rs | 3 --- src/export/pdf.rs | 54 ++++++++++++++++++++++++++++---------------- src/font/loader.rs | 49 ++++++++++++---------------------------- src/layout/boxed.rs | 6 ++--- src/lib.rs | 44 +++++++++++++++--------------------- test/shakespeare.tps | 2 -- 9 files changed, 106 insertions(+), 96 deletions(-) diff --git a/benches/complete.rs b/benches/complete.rs index 8bc4acabb..5b2010f41 100644 --- a/benches/complete.rs +++ b/benches/complete.rs @@ -1,19 +1,48 @@ use bencher::Bencher; use typeset::Typesetter; use typeset::font::FileSystemFontProvider; +use typeset::export::pdf::PdfExporter; -fn typesetting(b: &mut Bencher) { +fn prepare<'p>() -> (Typesetter<'p>, &'static str) { let src = include_str!("../test/shakespeare.tps"); let mut typesetter = Typesetter::new(); let provider = FileSystemFontProvider::from_listing("../fonts/fonts.toml").unwrap(); typesetter.add_font_provider(provider); + (typesetter, src) +} + +/// Benchmarks only the parsing step. +fn parsing(b: &mut Bencher) { + let (typesetter, src) = prepare(); + b.iter(|| { typesetter.parse(src).unwrap(); }); +} + +/// Benchmarks only the layouting step. +fn layouting(b: &mut Bencher) { + let (typesetter, src) = prepare(); + let tree = typesetter.parse(src).unwrap(); + b.iter(|| { typesetter.layout(&tree).unwrap(); }); +} + +/// Benchmarks the full typesetting step. +fn typesetting(b: &mut Bencher) { + let (typesetter, src) = prepare(); + b.iter(|| { typesetter.typeset(src).unwrap(); }); +} + +/// Benchmarks only the exporting step. +fn exporting(b: &mut Bencher) { + let (typesetter, src) = prepare(); + let doc = typesetter.typeset(src).unwrap(); + let exporter = PdfExporter::new(); b.iter(|| { - let _document = typesetter.typeset(&src).unwrap(); + let mut buf = Vec::new(); + exporter.export(&doc, &typesetter.loader(), &mut buf).unwrap(); }); } -bencher::benchmark_group!(benches, typesetting); +bencher::benchmark_group!(benches, parsing, layouting, typesetting, exporting); bencher::benchmark_main!(benches); diff --git a/benches/font.rs b/benches/font.rs index cac264ecb..cfcb5c1fc 100644 --- a/benches/font.rs +++ b/benches/font.rs @@ -3,10 +3,11 @@ use typeset::font::{*, FontClass::*}; use typeset::style::TextStyle; +/// Benchmarks just the char-by-char font loading. fn font_loading(b: &mut Bencher) { let provider = FileSystemFontProvider::from_listing("../fonts/fonts.toml").unwrap(); - let providers = vec![Box::new(provider) as Box]; - let font_loader = FontLoader::new(&providers); + let mut font_loader = FontLoader::new(); + font_loader.add_font_provider(provider); let text = include_str!("../test/shakespeare.tps"); @@ -28,7 +29,7 @@ fn font_loading(b: &mut Bencher) { match character { '_' => style.toggle_class(Italic), '*' => style.toggle_class(Bold), - '\n' => {}, + '\n' | '[' | ']' => {}, _ => { let _font = font_loader.get(FontQuery { character, diff --git a/src/bin/main.rs b/src/bin/main.rs index 7ba616459..df2cbc79c 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -54,7 +54,7 @@ fn run() -> Result<(), Box> { // Export the document into a PDF file. let exporter = PdfExporter::new(); let dest_file = File::create(&dest_path)?; - exporter.export(&document, BufWriter::new(dest_file))?; + exporter.export(&document, typesetter.loader(), BufWriter::new(dest_file))?; Ok(()) } diff --git a/src/doc.rs b/src/doc.rs index 0b4db73e6..d6a6096ee 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -1,6 +1,5 @@ //! Representation of typesetted documents. -use crate::font::Font; use crate::size::{Size, Size2D}; @@ -9,8 +8,6 @@ use crate::size::{Size, Size2D}; pub struct Document { /// The pages of the document. pub pages: Vec, - /// The fonts used (the page contents index into this). - pub fonts: Vec, } /// A page of a document. diff --git a/src/export/pdf.rs b/src/export/pdf.rs index aae624155..d01a9966d 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -1,6 +1,6 @@ //! Exporting into _PDF_ documents. -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::io::{self, Write}; use pdf::{PdfWriter, Ref, Rect, Version, Trailer, Content}; @@ -9,7 +9,7 @@ use pdf::font::{Type0Font, CIDFont, CIDFontType, CIDSystemInfo, FontDescriptor, use pdf::font::{GlyphUnit, CMap, CMapEncoding, WidthRecord, FontStream}; use crate::doc::{Document, Page as DocPage, LayoutAction}; -use crate::font::{Font, FontError}; +use crate::font::{Font, FontLoader, FontError}; use crate::size::{Size, Size2D}; @@ -26,8 +26,9 @@ impl 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)?; + pub fn export(&self, document: &Document, loader: &FontLoader, target: W) + -> PdfResult { + let mut engine = PdfEngine::new(document, loader, target)?; engine.write() } } @@ -38,6 +39,7 @@ struct PdfEngine<'d, W: Write> { writer: PdfWriter, doc: &'d Document, offsets: Offsets, + font_remap: HashMap, fonts: Vec, } @@ -53,41 +55,53 @@ struct Offsets { impl<'d, W: Write> PdfEngine<'d, W> { /// Create a new _PDF_ engine. - 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 contents = (pages.1 + 1, pages.1 + doc.pages.len() as Ref); - let fonts = (contents.1 + 1, contents.1 + 5 * doc.fonts.len() as Ref); - let offsets = Offsets { catalog, page_tree, pages, contents, fonts }; - + fn new(doc: &'d Document, loader: &FontLoader, target: W) -> PdfResult> { // Create a subsetted PDF font for each font in the document. + let mut font_remap = HashMap::new(); let fonts = { let mut font = 0usize; - let mut chars = vec![HashSet::new(); doc.fonts.len()]; + let mut chars = HashMap::new(); // Find out which characters are used for each font. for page in &doc.pages { for action in &page.actions { match action { - LayoutAction::WriteText(string) => chars[font].extend(string.chars()), - LayoutAction::SetFont(id, _) => font = *id, + LayoutAction::WriteText(string) => { + chars.entry(font) + .or_insert_with(HashSet::new) + .extend(string.chars()) + }, + LayoutAction::SetFont(id, _) => { + font = *id; + let new_id = font_remap.len(); + font_remap.entry(font).or_insert(new_id); + }, _ => {}, } } } - doc.fonts.iter() - .enumerate() - .map(|(i, font)| PdfFont::new(font, &chars[i])) + // Collect the fonts into a vector in the order of the values in the remapping. + let mut order = font_remap.iter().map(|(&old, &new)| (old, new)).collect::>(); + order.sort_by_key(|&(_, new)| new); + order.into_iter() + .map(|(old, _)| PdfFont::new(&loader.get_with_index(old), &chars[&old])) .collect::>>()? }; + // 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 contents = (pages.1 + 1, pages.1 + doc.pages.len() as Ref); + let font_offsets = (contents.1 + 1, contents.1 + 5 * fonts.len() as Ref); + let offsets = Offsets { catalog, page_tree, pages, contents, fonts: font_offsets }; + Ok(PdfEngine { writer: PdfWriter::new(target), doc, offsets, + font_remap, fonts, }) } @@ -151,7 +165,7 @@ impl<'d, W: Write> PdfEngine<'d, W> { for action in &page.actions { match action { LayoutAction::MoveAbsolute(pos) => next_pos = Some(*pos), - LayoutAction::SetFont(id, size) => next_font = Some((*id, *size)), + LayoutAction::SetFont(id, size) => next_font = Some((self.font_remap[id], *size)), LayoutAction::WriteText(string) => { // Flush the font if it is different from the current. if let Some((id, size)) = next_font { diff --git a/src/font/loader.rs b/src/font/loader.rs index a7c3bf4ad..a0e2dce1a 100644 --- a/src/font/loader.rs +++ b/src/font/loader.rs @@ -10,16 +10,14 @@ use super::{Font, FontInfo, FontClass, FontProvider}; /// Serves fonts matching queries. pub struct FontLoader<'p> { /// The font providers. - providers: Vec<&'p (dyn FontProvider + 'p)>, - /// The fonts available from each provider (indexed like `providers`). - infos: Vec<&'p [FontInfo]>, + providers: Vec>, /// The internal state. Uses interior mutability because the loader works behind /// an immutable reference to ease usage. - state: RefCell>, + state: RefCell, } /// Internal state of the font loader (seperated to wrap it in a `RefCell`). -struct FontLoaderState<'p> { +struct FontLoaderState { /// The loaded fonts alongside their external indices. Some fonts may not /// have external indices because they were loaded but did not contain the /// required character. However, these are still stored because they may @@ -28,21 +26,17 @@ struct FontLoaderState<'p> { /// Allows to retrieve a font (index) quickly if a query was submitted before. query_cache: HashMap, /// Allows to re-retrieve loaded fonts by their info instead of loading them again. - info_cache: HashMap<&'p FontInfo, usize>, + info_cache: HashMap, /// Indexed by external indices (the ones inside the tuples in the `fonts` vector) /// and maps to internal indices (the actual indices into the vector). inner_index: Vec, } impl<'p> FontLoader<'p> { - /// Create a new font loader using a set of providers. - pub fn new(providers: &'p [P]) -> FontLoader<'p> where P: AsRef { - let providers: Vec<_> = providers.iter().map(|p| p.as_ref()).collect(); - let infos = providers.iter().map(|prov| prov.available()).collect(); - + /// Create a new font loader. + pub fn new() -> FontLoader<'p> { FontLoader { - providers, - infos, + providers: vec![], state: RefCell::new(FontLoaderState { query_cache: HashMap::new(), info_cache: HashMap::new(), @@ -52,6 +46,11 @@ impl<'p> FontLoader<'p> { } } + /// Add a font provider to this loader. + pub fn add_font_provider(&mut self, provider: P) { + self.providers.push(Box::new(provider)); + } + /// Returns the font (and its index) best matching the query, if there is any. pub fn get(&self, query: FontQuery) -> Option<(usize, Ref)> { // Load results from the cache, if we had the exact same query before. @@ -70,8 +69,8 @@ impl<'p> FontLoader<'p> { // font that matches the first possible class. for class in &query.fallback { // For each class now go over all fonts from all font providers. - for (provider, infos) in self.providers.iter().zip(&self.infos) { - for info in infos.iter() { + for provider in &self.providers { + for info in provider.available().iter() { let viable = info.classes.contains(class); let matches = viable && query.classes.iter() .all(|class| info.classes.contains(class)); @@ -90,7 +89,7 @@ impl<'p> FontLoader<'p> { // Insert it into the storage and cache it by its info. let index = state.fonts.len(); - state.info_cache.insert(info, index); + state.info_cache.insert(info.clone(), index); state.fonts.push((None, font)); index @@ -139,23 +138,6 @@ impl<'p> FontLoader<'p> { let internal = state.inner_index[index]; Ref::map(state, |s| &s.fonts[internal].1) } - - /// Move the whole list of fonts out. - pub fn into_fonts(self) -> Vec { - // Sort the fonts by external index so that they are in the correct order. - // All fonts that were cached but not used by the outside are sorted to the back - // and are removed in the next step. - let mut fonts = self.state.into_inner().fonts; - fonts.sort_by_key(|&(maybe_index, _)| match maybe_index { - Some(index) => index, - None => std::usize::MAX, - }); - - // Remove the fonts that are not used from the outside. - fonts.into_iter().filter_map(|(maybe_index, font)| { - if maybe_index.is_some() { Some(font) } else { None } - }).collect() - } } impl Debug for FontLoader<'_> { @@ -163,7 +145,6 @@ impl Debug for FontLoader<'_> { let state = self.state.borrow(); f.debug_struct("FontLoader") .field("providers", &self.providers.len()) - .field("infos", &self.infos) .field("fonts", &state.fonts) .field("query_cache", &state.query_cache) .field("info_cache", &state.info_cache) diff --git a/src/layout/boxed.rs b/src/layout/boxed.rs index 15af278a2..a860b2675 100644 --- a/src/layout/boxed.rs +++ b/src/layout/boxed.rs @@ -1,7 +1,6 @@ //! Block-style layouting of boxes. use crate::doc::{Document, Page, LayoutAction}; -use crate::font::Font; use crate::size::{Size, Size2D}; use super::{ActionList, LayoutSpace, LayoutResult, LayoutError}; @@ -16,15 +15,14 @@ pub struct BoxLayout { } impl BoxLayout { - /// Convert this layout into a document given the list of fonts referenced by it. - pub fn into_doc(self, fonts: Vec) -> Document { + /// Convert this layout into a document. + pub fn into_doc(self) -> Document { Document { pages: vec![Page { width: self.dimensions.x, height: self.dimensions.y, actions: self.actions, }], - fonts, } } } diff --git a/src/lib.rs b/src/lib.rs index 9be3941d8..3dc669110 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,13 +38,11 @@ //! # */ //! # let file = File::create("../target/typeset-doc-hello.pdf").unwrap(); //! let exporter = PdfExporter::new(); -//! exporter.export(&document, file).unwrap(); +//! exporter.export(&document, typesetter.loader(), file).unwrap(); //! ``` -use std::fmt::{self, Debug, Formatter}; - use crate::doc::Document; -use crate::font::{Font, FontLoader, FontProvider}; +use crate::font::{FontLoader, FontProvider}; use crate::func::Scope; use crate::parsing::{parse, ParseContext, ParseResult, ParseError}; use crate::layout::{layout, LayoutContext, LayoutSpace, LayoutError, LayoutResult}; @@ -69,9 +67,10 @@ pub mod syntax; /// Transforms source code into typesetted documents. /// /// Can be configured through various methods. +#[derive(Debug)] pub struct Typesetter<'p> { - /// Font providers. - font_providers: Vec>, + /// The font loader shared by all typesetting processes. + loader: FontLoader<'p>, /// The default text style. text_style: TextStyle, /// The default page style. @@ -83,9 +82,9 @@ impl<'p> Typesetter<'p> { #[inline] pub fn new() -> Typesetter<'p> { Typesetter { + loader: FontLoader::new(), text_style: TextStyle::default(), page_style: PageStyle::default(), - font_providers: vec![], } } @@ -104,21 +103,19 @@ impl<'p> Typesetter<'p> { /// Add a font provider to the context of this typesetter. #[inline] pub fn add_font_provider(&mut self, provider: P) where P: FontProvider { - self.font_providers.push(Box::new(provider)); + self.loader.add_font_provider(provider); } /// Parse source code into a syntax tree. - #[inline] pub fn parse(&self, src: &str) -> ParseResult { let scope = Scope::with_std(); parse(src, ParseContext { scope: &scope }) } /// Layout a syntax tree and return the layout and the referenced font list. - pub fn layout(&self, tree: &SyntaxTree) -> LayoutResult<(BoxLayout, Vec)> { - let loader = FontLoader::new(&self.font_providers); + pub fn layout(&self, tree: &SyntaxTree) -> LayoutResult { let pages = layout(&tree, LayoutContext { - loader: &loader, + loader: &self.loader, style: &self.text_style, space: LayoutSpace { dimensions: self.page_style.dimensions, @@ -126,28 +123,23 @@ impl<'p> Typesetter<'p> { shrink_to_fit: false, }, })?; - Ok((pages, loader.into_fonts())) + Ok(pages) } /// Typeset a portable document from source code. - #[inline] pub fn typeset(&self, src: &str) -> Result { let tree = self.parse(src)?; - let (layout, fonts) = self.layout(&tree)?; - let document = layout.into_doc(fonts); + let layout = self.layout(&tree)?; + let document = layout.into_doc(); Ok(document) } + + /// A reference to the backing font loader. + pub fn loader(&self) -> &FontLoader<'p> { + &self.loader + } } -impl Debug for Typesetter<'_> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_struct("Typesetter") - .field("page_style", &self.page_style) - .field("text_style", &self.text_style) - .field("font_providers", &self.font_providers.len()) - .finish() - } -} /// The general error type for typesetting. pub enum TypesetError { @@ -193,7 +185,7 @@ mod test { let path = format!("../target/typeset-unit-{}.pdf", name); let file = BufWriter::new(File::create(path).unwrap()); let exporter = PdfExporter::new(); - exporter.export(&document, file).unwrap(); + exporter.export(&document, typesetter.loader(), file).unwrap(); } #[test] diff --git a/test/shakespeare.tps b/test/shakespeare.tps index 51526711c..e08393025 100644 --- a/test/shakespeare.tps +++ b/test/shakespeare.tps @@ -1,4 +1,3 @@ -// -------------------------------------------------------------------------- // [bold][Scene 5: _The Tower of London_] [italic][Enter Mortimer, brought in a chair, and Gaolers.] @@ -39,7 +38,6 @@ That so he might recover what was lost. -// -------------------------------------------------------------------------- // [italic][Enter Richard Plantagenet] *First Keeper.* My lord, your loving nephew now is come.