From 19be053cc3465229a39a65cab2460ac61e18cd8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurenz=20M=C3=A4dje?= Date: Sun, 28 Jul 2019 21:03:33 +0200 Subject: [PATCH] =?UTF-8?q?Create=20some=20benchmarks=20=F0=9F=93=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 12 ++++++ benches/complete.rs | 19 ++++++++++ benches/font.rs | 45 ++++++++++++++++++++++ src/bin/main.rs | 21 ++--------- src/font/mod.rs | 69 ++++++++++++++++++++++++++++++++-- src/lib.rs | 64 ++------------------------------ test/shakespeare.tps | 88 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 238 insertions(+), 80 deletions(-) create mode 100644 benches/complete.rs create mode 100644 benches/font.rs create mode 100644 test/shakespeare.tps diff --git a/Cargo.toml b/Cargo.toml index 5aa6ffa54..765353027 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,19 @@ opentype = { path = "../opentype" } byteorder = "1" smallvec = "0.6.10" unicode-xid = "0.1.0" +toml = "0.5" + +[dev-dependencies] +bencher = "0.1" [[bin]] name = "typst" path = "src/bin/main.rs" + +[[bench]] +name = "font" +harness = false + +[[bench]] +name = "complete" +harness = false diff --git a/benches/complete.rs b/benches/complete.rs new file mode 100644 index 000000000..8bc4acabb --- /dev/null +++ b/benches/complete.rs @@ -0,0 +1,19 @@ +use bencher::Bencher; +use typeset::Typesetter; +use typeset::font::FileSystemFontProvider; + + +fn typesetting(b: &mut Bencher) { + 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); + + b.iter(|| { + let _document = typesetter.typeset(&src).unwrap(); + }); +} + +bencher::benchmark_group!(benches, typesetting); +bencher::benchmark_main!(benches); diff --git a/benches/font.rs b/benches/font.rs new file mode 100644 index 000000000..cac264ecb --- /dev/null +++ b/benches/font.rs @@ -0,0 +1,45 @@ +use bencher::Bencher; +use typeset::font::{*, FontClass::*}; +use typeset::style::TextStyle; + + +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 text = include_str!("../test/shakespeare.tps"); + + let mut style = TextStyle { + classes: vec![Regular], + fallback: vec![ + Family("Helvetica".to_string()), + Family("Computer Modern".to_string()), + Serif, + Monospace, + ], + font_size: 12.0, + line_spacing: 1.0, + paragraph_spacing: 1.0, + }; + + b.iter(|| { + for character in text.chars() { + match character { + '_' => style.toggle_class(Italic), + '*' => style.toggle_class(Bold), + '\n' => {}, + _ => { + let _font = font_loader.get(FontQuery { + character, + classes: style.classes.clone(), + fallback: style.fallback.clone(), + }).unwrap(); + }, + } + } + }); +} + +bencher::benchmark_group!(benches, font_loading); +bencher::benchmark_main!(benches); diff --git a/src/bin/main.rs b/src/bin/main.rs index 612375807..7ba616459 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -6,7 +6,7 @@ use std::path::{Path, PathBuf}; use std::process; use typeset::Typesetter; -use typeset::{font::FileSystemFontProvider, font}; +use typeset::font::FileSystemFontProvider; use typeset::export::pdf::PdfExporter; @@ -18,7 +18,7 @@ fn main() { } /// The actual main function. -fn run() -> Result<(), Box> { +fn run() -> Result<(), Box> { let args: Vec = env::args().collect(); if args.len() < 2 || args.len() > 3 { help_and_quit(); @@ -45,21 +45,8 @@ fn run() -> Result<(), Box> { // Create a typesetter with a font provider that provides the default fonts. let mut typesetter = Typesetter::new(); - typesetter.add_font_provider(FileSystemFontProvider::new("../fonts", vec![ - ("CMU-SansSerif-Regular.ttf", font!["Computer Modern", Regular, SansSerif]), - ("CMU-SansSerif-Italic.ttf", font!["Computer Modern", Italic, SansSerif]), - ("CMU-SansSerif-Bold.ttf", font!["Computer Modern", Bold, SansSerif]), - ("CMU-SansSerif-Bold-Italic.ttf", font!["Computer Modern", Bold, Italic, SansSerif]), - ("CMU-Serif-Regular.ttf", font!["Computer Modern", Regular, Serif]), - ("CMU-Serif-Italic.ttf", font!["Computer Modern", Italic, Serif]), - ("CMU-Serif-Bold.ttf", font!["Computer Modern", Bold, Serif]), - ("CMU-Serif-Bold-Italic.ttf", font!["Computer Modern", Bold, Italic, Serif]), - ("CMU-Typewriter-Regular.ttf", font!["Computer Modern", Regular, Serif, SansSerif, Monospace]), - ("CMU-Typewriter-Italic.ttf", font!["Computer Modern", Italic, Serif, SansSerif, Monospace]), - ("CMU-Typewriter-Bold.ttf", font!["Computer Modern", Bold, Serif, SansSerif, Monospace]), - ("CMU-Typewriter-Bold-Italic.ttf", font!["Computer Modern", Bold, Italic, Serif, SansSerif, Monospace]), - ("NotoEmoji-Regular.ttf", font!["Noto", Regular, Bold, Italic, SansSerif, Serif, Monospace]), - ])); + let provider = FileSystemFontProvider::from_listing("fonts/fonts.toml").unwrap(); + typesetter.add_font_provider(provider); // Typeset the source code. let document = typesetter.typeset(&src)?; diff --git a/src/font/mod.rs b/src/font/mod.rs index 80d900dc6..4efc624d8 100644 --- a/src/font/mod.rs +++ b/src/font/mod.rs @@ -11,13 +11,15 @@ //! from a folder on the file system. use std::collections::HashMap; -use std::fs::File; +use std::fs::{self, File}; use std::io::{self, Cursor, Read, Seek, BufReader}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use opentype::{Error as OpentypeError, OpenTypeReader}; use opentype::tables::{Header, Name, CharMap, HorizontalMetrics, Post, OS2}; use opentype::types::{MacStyleFlags, NameEntry}; +use toml::map::Map as TomlMap; +use toml::value::Value as TomlValue; use self::subset::Subsetter; use crate::size::Size; @@ -300,6 +302,64 @@ impl FileSystemFontProvider { infos, } } + + /// Create a new provider from a font listing file. + pub fn from_listing>(file: P) -> FontResult { + fn inv(message: S) -> FontError { + FontError::InvalidListing(message.to_string()) + } + + let file = file.as_ref(); + let base = file.parent() + .ok_or_else(|| inv("expected listings file"))?; + + let bytes = fs::read(file)?; + let map: TomlMap = toml::de::from_slice(&bytes) + .map_err(|err| inv(err))?; + + let mut paths = Vec::new(); + let mut infos = Vec::new(); + + for value in map.values() { + if let TomlValue::Table(table) = value { + // Parse the string file key. + paths.push(match table.get("file") { + Some(TomlValue::String(s)) => PathBuf::from(s), + _ => return Err(inv("expected file name")), + }); + + // Parse the array classes key. + infos.push(if let Some(TomlValue::Array(array)) = table.get("classes") { + let mut classes = Vec::with_capacity(array.len()); + for class in array { + classes.push(match class { + TomlValue::String(class) => match class.as_str() { + "Serif" => FontClass::Serif, + "SansSerif" => FontClass::SansSerif, + "Monospace" => FontClass::Monospace, + "Regular" => FontClass::Regular, + "Bold" => FontClass::Bold, + "Italic" => FontClass::Italic, + _ => FontClass::Family(class.to_string()), + }, + _ => return Err(inv("expect font class string")), + }) + } + FontInfo { classes } + } else { + return Err(inv("expected font classes")); + }); + } else { + return Err(inv("expected file/classes table")); + } + } + + Ok(FileSystemFontProvider { + base: base.to_owned(), + paths, + infos, + }) + } } impl FontProvider for FileSystemFontProvider { @@ -309,7 +369,7 @@ impl FontProvider for FileSystemFontProvider { let path = &self.paths[index]; let full_path = self.base.join(path); let file = File::open(full_path).ok()?; - Some(Box::new(BufReader::new(file)) as Box) + Some(Box::new(BufReader::new(file)) as Box) } #[inline] @@ -323,6 +383,8 @@ impl FontProvider for FileSystemFontProvider { pub enum FontError { /// The font file is incorrect. InvalidFont(String), + /// The font listing is incorrect. + InvalidListing(String), /// A character requested for subsetting was not present in the source font. MissingCharacter(char), /// A requested or required table was not present. @@ -340,6 +402,7 @@ error_type! { res: FontResult, show: f => match err { FontError::InvalidFont(message) => write!(f, "invalid font: {}", message), + FontError::InvalidListing(message) => write!(f, "invalid font listing: {}", message), FontError::MissingCharacter(c) => write!(f, "missing character: '{}'", c), FontError::MissingTable(table) => write!(f, "missing table: '{}'", table), FontError::UnsupportedTable(table) => write!(f, "unsupported table: {}", table), diff --git a/src/lib.rs b/src/lib.rs index ed1e079ed..9be3941d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -183,21 +183,8 @@ mod test { /// Create a _PDF_ with a name from the source code. fn test(name: &str, src: &str) { let mut typesetter = Typesetter::new(); - typesetter.add_font_provider(FileSystemFontProvider::new("../fonts", vec![ - ("CMU-SansSerif-Regular.ttf", font!["Computer Modern", Regular, SansSerif]), - ("CMU-SansSerif-Italic.ttf", font!["Computer Modern", Italic, SansSerif]), - ("CMU-SansSerif-Bold.ttf", font!["Computer Modern", Bold, SansSerif]), - ("CMU-SansSerif-Bold-Italic.ttf", font!["Computer Modern", Bold, Italic, SansSerif]), - ("CMU-Serif-Regular.ttf", font!["Computer Modern", Regular, Serif]), - ("CMU-Serif-Italic.ttf", font!["Computer Modern", Italic, Serif]), - ("CMU-Serif-Bold.ttf", font!["Computer Modern", Bold, Serif]), - ("CMU-Serif-Bold-Italic.ttf", font!["Computer Modern", Bold, Italic, Serif]), - ("CMU-Typewriter-Regular.ttf", font!["Computer Modern", Regular, Serif, SansSerif, Monospace]), - ("CMU-Typewriter-Italic.ttf", font!["Computer Modern", Italic, Serif, SansSerif, Monospace]), - ("CMU-Typewriter-Bold.ttf", font!["Computer Modern", Bold, Serif, SansSerif, Monospace]), - ("CMU-Typewriter-Bold-Italic.ttf", font!["Computer Modern", Bold, Italic, Serif, SansSerif, Monospace]), - ("NotoEmoji-Regular.ttf", font!["Noto", Regular, Bold, Italic, SansSerif, Serif, Monospace]), - ])); + let provider = FileSystemFontProvider::from_listing("../fonts/fonts.toml").unwrap(); + typesetter.add_font_provider(provider); // Typeset into document. let document = typesetter.typeset(src).unwrap(); @@ -231,50 +218,7 @@ mod test { } #[test] - fn wikipedia() { - test("wikipedia", r#" - Typesetting is the composition of text by means of arranging physical types or the - digital equivalents. Stored letters and other symbols (called sorts in mechanical - systems and glyphs in digital systems) are retrieved and ordered according to a - language's orthography for visual display. Typesetting requires one or more fonts - (which are widely but erroneously confused with and substituted for typefaces). One - significant effect of typesetting was that authorship of works could be spotted more - easily, making it difficult for copiers who have not gained permission. - - During much of the letterpress era, movable type was composed by hand for each page. - Cast metal sorts were composed into words, then lines, then paragraphs, then pages of - text and tightly bound together to make up a form, with all letter faces exactly the - same "height to paper", creating an even surface of type. The form was placed in a - press, inked, and an impression made on paper. - - During typesetting, individual sorts are picked from a type case with the right hand, - and set into a composing stick held in the left hand from left to right, and as viewed - by the setter upside down. As seen in the photo of the composing stick, a lower case - 'q' looks like a 'd', a lower case 'b' looks like a 'p', a lower case 'p' looks like a - 'b' and a lower case 'd' looks like a 'q'. This is reputed to be the origin of the - expression "mind your p's and q's". It might just as easily have been "mind your b's - and d's". - - The diagram at right illustrates a cast metal sort: a face, b body or shank, c point - size, 1 shoulder, 2 nick, 3 groove, 4 foot. Wooden printing sorts were in use for - centuries in combination with metal type. Not shown, and more the concern of the - casterman, is the “set”, or width of each sort. Set width, like body size, is measured - in points. - - In order to extend the working life of type, and to account for the finite sorts in a - case of type, copies of forms were cast when anticipating subsequent printings of a - text, freeing the costly type for other work. This was particularly prevalent in book - and newspaper work where rotary presses required type forms to wrap an impression - cylinder rather than set in the bed of a press. In this process, called stereotyping, - the entire form is pressed into a fine matrix such as plaster of Paris or papier mâché - called a flong to create a positive, from which the stereotype form was electrotyped, - cast of type metal. - - Advances such as the typewriter and computer would push the state of the art even - farther ahead. Still, hand composition and letterpress printing have not fallen - completely out of use, and since the introduction of digital typesetting, it has seen a - revival as an artisanal pursuit. However, it is a very small niche within the larger - typesetting market. - "#); + fn shakespeare() { + test("shakespeare", include_str!("../test/shakespeare.tps")); } } diff --git a/test/shakespeare.tps b/test/shakespeare.tps new file mode 100644 index 000000000..51526711c --- /dev/null +++ b/test/shakespeare.tps @@ -0,0 +1,88 @@ +// -------------------------------------------------------------------------- // +[bold][Scene 5: _The Tower of London_] + +[italic][Enter Mortimer, brought in a chair, and Gaolers.] + +*Mortimer.* Kind keepers of my weak decaying age, + Let dying Mortimer here rest himself. + Even like a man new haled from the rack, + So fare my limbs with long imprisonment; + And these grey locks, the pursuivants of death, + Nestor-like aged in an age of care, + Argue the end of Edmund Mortimer. + These eyes, like lamps whose wasting oil is spent, + Wax dim, as drawing to their exigent; + Weak shoulders, overborne with burdening grief, + And pithless arms, like to a withered vine + That droops his sapless branches to the ground. + Yet are these feet, whose strengthless stay is numb, + Unable to support this lump of clay, + Swift-winged with desire to get a grave, + As witting I no other comfort have. + But tell me, keeper, will my nephew come? + +*First Keeper.* Richard Plantagenet, my lord, will come. + We sent unto the Temple, unto his chamber; + And answer was return'd that he will come. + +*Mortimer.* Enough; my soul shall then be satisfied. + Poor gentleman! his wrong doth equal mine. + Since Henry Monmouth first began to reign, + Before whose glory I was great in arms, + This loathsome sequestration have I had; + And even since then hath Richard been obscur'd, + Depriv'd of honour and inheritance. + But now the arbitrator of despairs, + Just Death, kind umpire of men's miseries, + With sweet enlargement doth dismiss me hence. + I would his troubles likewise were expir'd, + That so he might recover what was lost. + + +// -------------------------------------------------------------------------- // +[italic][Enter Richard Plantagenet] + +*First Keeper.* My lord, your loving nephew now is come. + +*Mortimer.* Richard Plantagenet, my friend, is he come? + +*Plantagenet.* Ay, noble uncle, thus ignobly us'd, + Your nephew, late despised Richard, comes. + +*Mortimer.* Direct mine arms I may embrace his neck + And in his bosom spend my latter gasp. + O, tell me when my lips do touch his cheeks, + That I may kindly give one fainting kiss. + And now declare, sweet stem from York's great stock, + Why didst thou say of late thou wert despis'd? + +*Plantagenet.* First, lean thine aged back against mine arm; + And, in that ease, I'll tell thee my disease. + This day, in argument upon a case, + Some words there grew 'twixt Somerset and me; + Among which terms he us'd his lavish tongue + And did upbraid me with my father's death; + Which obloquy set bars before my tongue, + Else with the like I had requited him. + Therefore, good uncle, for my father's sake, + In honour of a true Plantagenet, + And for alliance sake, declare the cause + My father, Earl of Cambridge, lost his head. + +*Mortimer.* That cause, fair nephew, that imprison'd me + And hath detain'd me all my flow'ring youth + Within a loathsome dungeon, there to pine, + Was cursed instrument of his decease. + +*Plantagenet.* Discover more at large what cause that was, + For I am ignorant and cannot guess. + +*Mortimer.* I will, if that my fading breath permit + And death approach not ere my tale be done. + Henry the Fourth, grandfather to this king, + Depos'd his nephew Richard, Edward's son, + The first-begotten and the lawful heir + Of Edward king, the third of that descent; + During whose reign the Percies of the north, + Finding his usurpation most unjust, + Endeavour'd my advancement to the throne ...