From dd4a4545a6b72e48cde5d2483fac5e4e76f6047f Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 12 Oct 2020 21:26:58 +0200 Subject: =?UTF-8?q?Move=20main=20back=20into=20src/=20=F0=9F=93=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 28 +++--- main/Cargo.toml | 10 --- main/src/main.rs | 75 ---------------- src/main.rs | 75 ++++++++++++++++ tests/test_typeset.rs | 238 ------------------------------------------------- tests/typeset.rs | 239 ++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 327 insertions(+), 338 deletions(-) delete mode 100644 main/Cargo.toml delete mode 100644 main/src/main.rs create mode 100644 src/main.rs delete mode 100644 tests/test_typeset.rs create mode 100644 tests/typeset.rs diff --git a/Cargo.toml b/Cargo.toml index 21c36b6d..49833b26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,18 +4,6 @@ version = "0.1.0" authors = ["The Typst Project Developers"] edition = "2018" -[workspace] -members = ["main"] - -[lib] -bench = false - -[profile.dev.package."*"] -opt-level = 2 - -[profile.release] -lto = true - [features] default = ["fs"] fs = ["fontdock/fs"] @@ -31,17 +19,27 @@ serde = { version = "1", features = ["derive"], optional = true } [dev-dependencies] criterion = "0.3" -kurbo = "0.6.3" raqote = { version = "0.8", default-features = false } +[profile.dev.package."*"] +opt-level = 2 + +[profile.release] +lto = true + +[lib] +bench = false + +[[bin]] +name = "typstc" +required-features = ["fs"] + [[test]] name = "typeset" -path = "tests/test_typeset.rs" required-features = ["fs"] harness = false [[bench]] name = "benchmarks" -path = "benches/benchmarks.rs" required-features = ["fs"] harness = false diff --git a/main/Cargo.toml b/main/Cargo.toml deleted file mode 100644 index 65e61cfd..00000000 --- a/main/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "typstc-main" -version = "0.1.0" -authors = ["The Typst Project Developers"] -edition = "2018" - -[dependencies] -typstc = { path = ".." } -fontdock = { path = "../../fontdock" } -futures-executor = "0.3" diff --git a/main/src/main.rs b/main/src/main.rs deleted file mode 100644 index 9bf693e4..00000000 --- a/main/src/main.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::cell::RefCell; -use std::fs::{read_to_string, File}; -use std::io::BufWriter; -use std::path::{Path, PathBuf}; -use std::rc::Rc; - -use fontdock::fs::{FsIndex, FsSource}; - -use typstc::diag::{Feedback, Pass}; -use typstc::eval::State; -use typstc::export::pdf; -use typstc::font::FontLoader; -use typstc::parse::LineMap; -use typstc::typeset; - -fn main() { - let args: Vec<_> = std::env::args().collect(); - if args.len() < 2 || args.len() > 3 { - println!("Usage: typst src.typ [out.pdf]"); - return; - } - - let src_path = Path::new(&args[1]); - let dest_path = if args.len() <= 2 { - src_path.with_extension("pdf") - } else { - PathBuf::from(&args[2]) - }; - - if src_path == dest_path { - panic!("source and destination path are the same"); - } - - let src = read_to_string(src_path).expect("failed to read from source file"); - - let mut index = FsIndex::new(); - index.search_dir("fonts"); - index.search_os(); - - let (files, descriptors) = index.into_vecs(); - let loader = Rc::new(RefCell::new(FontLoader::new( - Box::new(FsSource::new(files)), - descriptors, - ))); - - let state = State::default(); - let Pass { - output: layouts, - feedback: Feedback { mut diags, .. }, - } = typeset(&src, state, Rc::clone(&loader)); - - if !diags.is_empty() { - diags.sort(); - - let map = LineMap::new(&src); - for diag in diags { - let span = diag.span; - let start = map.location(span.start); - let end = map.location(span.end); - println!( - " {}: {}:{}-{}: {}", - diag.v.level, - src_path.display(), - start, - end, - diag.v.message, - ); - } - } - - let loader = loader.borrow(); - let file = File::create(&dest_path).expect("failed to create output file"); - let writer = BufWriter::new(file); - pdf::export(&layouts, &loader, writer).expect("failed to export pdf"); -} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000..9bf693e4 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,75 @@ +use std::cell::RefCell; +use std::fs::{read_to_string, File}; +use std::io::BufWriter; +use std::path::{Path, PathBuf}; +use std::rc::Rc; + +use fontdock::fs::{FsIndex, FsSource}; + +use typstc::diag::{Feedback, Pass}; +use typstc::eval::State; +use typstc::export::pdf; +use typstc::font::FontLoader; +use typstc::parse::LineMap; +use typstc::typeset; + +fn main() { + let args: Vec<_> = std::env::args().collect(); + if args.len() < 2 || args.len() > 3 { + println!("Usage: typst src.typ [out.pdf]"); + return; + } + + let src_path = Path::new(&args[1]); + let dest_path = if args.len() <= 2 { + src_path.with_extension("pdf") + } else { + PathBuf::from(&args[2]) + }; + + if src_path == dest_path { + panic!("source and destination path are the same"); + } + + let src = read_to_string(src_path).expect("failed to read from source file"); + + let mut index = FsIndex::new(); + index.search_dir("fonts"); + index.search_os(); + + let (files, descriptors) = index.into_vecs(); + let loader = Rc::new(RefCell::new(FontLoader::new( + Box::new(FsSource::new(files)), + descriptors, + ))); + + let state = State::default(); + let Pass { + output: layouts, + feedback: Feedback { mut diags, .. }, + } = typeset(&src, state, Rc::clone(&loader)); + + if !diags.is_empty() { + diags.sort(); + + let map = LineMap::new(&src); + for diag in diags { + let span = diag.span; + let start = map.location(span.start); + let end = map.location(span.end); + println!( + " {}: {}:{}-{}: {}", + diag.v.level, + src_path.display(), + start, + end, + diag.v.message, + ); + } + } + + let loader = loader.borrow(); + let file = File::create(&dest_path).expect("failed to create output file"); + let writer = BufWriter::new(file); + pdf::export(&layouts, &loader, writer).expect("failed to export pdf"); +} diff --git a/tests/test_typeset.rs b/tests/test_typeset.rs deleted file mode 100644 index 8322dd08..00000000 --- a/tests/test_typeset.rs +++ /dev/null @@ -1,238 +0,0 @@ -use std::cell::RefCell; -use std::env; -use std::ffi::OsStr; -use std::fs::{self, File}; -use std::io::BufWriter; -use std::path::Path; -use std::rc::Rc; - -use fontdock::fs::{FsIndex, FsSource}; -use raqote::{DrawTarget, PathBuilder, SolidSource, Source, Transform, Vector}; -use ttf_parser::OutlineBuilder; - -use typstc::diag::{Feedback, Pass}; -use typstc::eval::State; -use typstc::export::pdf; -use typstc::font::{FontLoader, SharedFontLoader}; -use typstc::geom::{Length, Point}; -use typstc::layout::{BoxLayout, LayoutElement}; -use typstc::parse::LineMap; -use typstc::shaping::Shaped; -use typstc::typeset; - -const TEST_DIR: &str = "tests"; -const OUT_DIR: &str = "tests/out"; -const FONT_DIR: &str = "fonts"; - -const BLACK: SolidSource = SolidSource { r: 0, g: 0, b: 0, a: 255 }; -const WHITE: SolidSource = SolidSource { r: 255, g: 255, b: 255, a: 255 }; - -fn main() { - let filter = TestFilter::new(env::args().skip(1)); - let mut filtered = Vec::new(); - - for entry in fs::read_dir(TEST_DIR).unwrap() { - let path = entry.unwrap().path(); - if path.extension() != Some(OsStr::new("typ")) { - continue; - } - - let name = path.file_stem().unwrap().to_string_lossy().to_string(); - if filter.matches(&name) { - let src = fs::read_to_string(&path).unwrap(); - filtered.push((name, path, src)); - } - } - - let len = filtered.len(); - if len == 0 { - return; - } else if len == 1 { - println!("Running test ..."); - } else { - println!("Running {} tests", len); - } - - fs::create_dir_all(OUT_DIR).unwrap(); - - let mut index = FsIndex::new(); - index.search_dir(FONT_DIR); - - let (files, descriptors) = index.into_vecs(); - let loader = Rc::new(RefCell::new(FontLoader::new( - Box::new(FsSource::new(files)), - descriptors, - ))); - - for (name, path, src) in filtered { - test(&name, &src, &path, &loader) - } -} - -fn test(name: &str, src: &str, src_path: &Path, loader: &SharedFontLoader) { - println!("Testing {}.", name); - - let state = State::default(); - let Pass { - output: layouts, - feedback: Feedback { mut diags, .. }, - } = typeset(&src, state, Rc::clone(loader)); - - if !diags.is_empty() { - diags.sort(); - - let map = LineMap::new(&src); - for diag in diags { - let span = diag.span; - let start = map.location(span.start); - let end = map.location(span.end); - println!( - " {}: {}:{}-{}: {}", - diag.v.level, - src_path.display(), - start, - end, - diag.v.message, - ); - } - } - - let loader = loader.borrow(); - - let png_path = format!("{}/{}.png", OUT_DIR, name); - render(&layouts, &loader, 3.0).write_png(png_path).unwrap(); - - let pdf_path = format!("{}/{}.pdf", OUT_DIR, name); - let file = BufWriter::new(File::create(pdf_path).unwrap()); - pdf::export(&layouts, &loader, file).unwrap(); -} - -struct TestFilter { - filter: Vec, - perfect: bool, -} - -impl TestFilter { - fn new(args: impl Iterator) -> Self { - let mut filter = Vec::new(); - let mut perfect = false; - - for arg in args { - match arg.as_str() { - "--nocapture" => {} - "=" => perfect = true, - _ => filter.push(arg), - } - } - - Self { filter, perfect } - } - - fn matches(&self, name: &str) -> bool { - if self.perfect { - self.filter.iter().any(|p| name == p) - } else { - self.filter.is_empty() || self.filter.iter().any(|p| name.contains(p)) - } - } -} - -fn render(layouts: &[BoxLayout], loader: &FontLoader, scale: f64) -> DrawTarget { - let pad = Length::pt(scale * 10.0); - let width = 2.0 * pad - + layouts - .iter() - .map(|l| scale * l.size.width) - .max_by(|a, b| a.partial_cmp(&b).unwrap()) - .unwrap(); - - let height = - pad + layouts.iter().map(|l| scale * l.size.height + pad).sum::(); - - let int_width = width.to_pt().round() as i32; - let int_height = height.to_pt().round() as i32; - let mut surface = DrawTarget::new(int_width, int_height); - surface.clear(BLACK); - - let mut offset = Point::new(pad, pad); - for layout in layouts { - surface.fill_rect( - offset.x.to_pt() as f32, - offset.y.to_pt() as f32, - (scale * layout.size.width).to_pt() as f32, - (scale * layout.size.height).to_pt() as f32, - &Source::Solid(WHITE), - &Default::default(), - ); - - for &(pos, ref element) in &layout.elements { - match element { - LayoutElement::Text(shaped) => render_shaped( - &mut surface, - loader, - shaped, - scale * pos + offset, - scale, - ), - } - } - - offset.y += scale * layout.size.height + pad; - } - - surface -} - -fn render_shaped( - surface: &mut DrawTarget, - loader: &FontLoader, - shaped: &Shaped, - pos: Point, - scale: f64, -) { - let face = loader.get_loaded(shaped.face).get(); - - for (&glyph, &offset) in shaped.glyphs.iter().zip(&shaped.offsets) { - let mut builder = WrappedPathBuilder(PathBuilder::new()); - face.outline_glyph(glyph, &mut builder); - let path = builder.0.finish(); - - let units_per_em = face.units_per_em().unwrap_or(1000); - let s = scale * (shaped.font_size / units_per_em as f64); - let x = pos.x + scale * offset; - let y = pos.y + scale * shaped.font_size; - - let t = Transform::create_scale(s.to_pt() as f32, -s.to_pt() as f32) - .post_translate(Vector::new(x.to_pt() as f32, y.to_pt() as f32)); - - surface.fill( - &path.transform(&t), - &Source::Solid(SolidSource { r: 0, g: 0, b: 0, a: 255 }), - &Default::default(), - ) - } -} - -struct WrappedPathBuilder(PathBuilder); - -impl OutlineBuilder for WrappedPathBuilder { - fn move_to(&mut self, x: f32, y: f32) { - self.0.move_to(x, y); - } - - fn line_to(&mut self, x: f32, y: f32) { - self.0.line_to(x, y); - } - - fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { - self.0.quad_to(x1, y1, x, y); - } - - fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { - self.0.cubic_to(x1, y1, x2, y2, x, y); - } - - fn close(&mut self) { - self.0.close(); - } -} diff --git a/tests/typeset.rs b/tests/typeset.rs new file mode 100644 index 00000000..8ba1ed47 --- /dev/null +++ b/tests/typeset.rs @@ -0,0 +1,239 @@ +use std::cell::RefCell; +use std::env; +use std::ffi::OsStr; +use std::fs::{self, File}; +use std::io::BufWriter; +use std::path::Path; +use std::rc::Rc; + +use fontdock::fs::{FsIndex, FsSource}; +use raqote::{DrawTarget, PathBuilder, SolidSource, Source, Transform, Vector}; +use ttf_parser::OutlineBuilder; + +use typstc::diag::{Feedback, Pass}; +use typstc::eval::State; +use typstc::export::pdf; +use typstc::font::{FontLoader, SharedFontLoader}; +use typstc::geom::{Length, Point}; +use typstc::layout::{BoxLayout, LayoutElement}; +use typstc::parse::LineMap; +use typstc::shaping::Shaped; +use typstc::typeset; + +const TEST_DIR: &str = "tests"; +const OUT_DIR: &str = "tests/out"; +const FONT_DIR: &str = "fonts"; + +const BLACK: SolidSource = SolidSource { r: 0, g: 0, b: 0, a: 255 }; +const WHITE: SolidSource = SolidSource { r: 255, g: 255, b: 255, a: 255 }; + +fn main() { + let filter = TestFilter::new(env::args().skip(1)); + let mut filtered = Vec::new(); + + for entry in fs::read_dir(TEST_DIR).unwrap() { + let path = entry.unwrap().path(); + if path.extension() != Some(OsStr::new("typ")) { + continue; + } + + let name = path.file_stem().unwrap().to_string_lossy().to_string(); + if filter.matches(&name) { + let src = fs::read_to_string(&path).unwrap(); + filtered.push((name, path, src)); + } + } + + let len = filtered.len(); + if len == 0 { + return; + } else if len == 1 { + println!("Running test ..."); + } else { + println!("Running {} tests", len); + } + + fs::create_dir_all(OUT_DIR).unwrap(); + + let mut index = FsIndex::new(); + index.search_dir(FONT_DIR); + + let (files, descriptors) = index.into_vecs(); + let loader = Rc::new(RefCell::new(FontLoader::new( + Box::new(FsSource::new(files)), + descriptors, + ))); + + for (name, path, src) in filtered { + test(&name, &src, &path, &loader) + } +} + +fn test(name: &str, src: &str, src_path: &Path, loader: &SharedFontLoader) { + println!("Testing {}.", name); + + let state = State::default(); + let Pass { + output: layouts, + feedback: Feedback { mut diags, .. }, + } = typeset(&src, state, Rc::clone(loader)); + + if !diags.is_empty() { + diags.sort(); + + let map = LineMap::new(&src); + for diag in diags { + let span = diag.span; + let start = map.location(span.start); + let end = map.location(span.end); + println!( + " {}: {}:{}-{}: {}", + diag.v.level, + src_path.display(), + start, + end, + diag.v.message, + ); + } + } + + let loader = loader.borrow(); + + let png_path = format!("{}/{}.png", OUT_DIR, name); + let surface = render(&layouts, &loader, 3.0); + surface.write_png(png_path).unwrap(); + + let pdf_path = format!("{}/{}.pdf", OUT_DIR, name); + let file = BufWriter::new(File::create(pdf_path).unwrap()); + pdf::export(&layouts, &loader, file).unwrap(); +} + +struct TestFilter { + filter: Vec, + perfect: bool, +} + +impl TestFilter { + fn new(args: impl Iterator) -> Self { + let mut filter = Vec::new(); + let mut perfect = false; + + for arg in args { + match arg.as_str() { + "--nocapture" => {} + "=" => perfect = true, + _ => filter.push(arg), + } + } + + Self { filter, perfect } + } + + fn matches(&self, name: &str) -> bool { + if self.perfect { + self.filter.iter().any(|p| name == p) + } else { + self.filter.is_empty() || self.filter.iter().any(|p| name.contains(p)) + } + } +} + +fn render(layouts: &[BoxLayout], loader: &FontLoader, scale: f64) -> DrawTarget { + let pad = Length::pt(scale * 10.0); + let width = 2.0 * pad + + layouts + .iter() + .map(|l| scale * l.size.width) + .max_by(|a, b| a.partial_cmp(&b).unwrap()) + .unwrap(); + + let height = + pad + layouts.iter().map(|l| scale * l.size.height + pad).sum::(); + + let int_width = width.to_pt().round() as i32; + let int_height = height.to_pt().round() as i32; + let mut surface = DrawTarget::new(int_width, int_height); + surface.clear(BLACK); + + let mut offset = Point::new(pad, pad); + for layout in layouts { + surface.fill_rect( + offset.x.to_pt() as f32, + offset.y.to_pt() as f32, + (scale * layout.size.width).to_pt() as f32, + (scale * layout.size.height).to_pt() as f32, + &Source::Solid(WHITE), + &Default::default(), + ); + + for &(pos, ref element) in &layout.elements { + match element { + LayoutElement::Text(shaped) => render_shaped( + &mut surface, + loader, + shaped, + scale * pos + offset, + scale, + ), + } + } + + offset.y += scale * layout.size.height + pad; + } + + surface +} + +fn render_shaped( + surface: &mut DrawTarget, + loader: &FontLoader, + shaped: &Shaped, + pos: Point, + scale: f64, +) { + let face = loader.get_loaded(shaped.face).get(); + + for (&glyph, &offset) in shaped.glyphs.iter().zip(&shaped.offsets) { + let mut builder = WrappedPathBuilder(PathBuilder::new()); + face.outline_glyph(glyph, &mut builder); + let path = builder.0.finish(); + + let units_per_em = face.units_per_em().unwrap_or(1000); + let s = scale * (shaped.font_size / units_per_em as f64); + let x = pos.x + scale * offset; + let y = pos.y + scale * shaped.font_size; + + let t = Transform::create_scale(s.to_pt() as f32, -s.to_pt() as f32) + .post_translate(Vector::new(x.to_pt() as f32, y.to_pt() as f32)); + + surface.fill( + &path.transform(&t), + &Source::Solid(SolidSource { r: 0, g: 0, b: 0, a: 255 }), + &Default::default(), + ) + } +} + +struct WrappedPathBuilder(PathBuilder); + +impl OutlineBuilder for WrappedPathBuilder { + fn move_to(&mut self, x: f32, y: f32) { + self.0.move_to(x, y); + } + + fn line_to(&mut self, x: f32, y: f32) { + self.0.line_to(x, y); + } + + fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { + self.0.quad_to(x1, y1, x, y); + } + + fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { + self.0.cubic_to(x1, y1, x2, y2, x, y); + } + + fn close(&mut self) { + self.0.close(); + } +} -- cgit v1.2.3