2019-12-08 11:15:04 +01:00
use std ::collections ::HashMap ;
use std ::error ::Error ;
use std ::ffi ::OsStr ;
use std ::fs ::{ File , create_dir_all , read_dir , read_to_string } ;
use std ::io ::{ BufWriter , Write } ;
use std ::panic ;
2019-10-11 17:53:28 +02:00
use std ::process ::Command ;
2019-10-16 21:31:14 +02:00
2020-01-06 12:41:42 +01:00
use futures_executor ::block_on ;
2019-12-08 11:15:04 +01:00
use typstc ::Typesetter ;
use typstc ::layout ::{ MultiLayout , Serialize } ;
2019-12-15 15:09:09 +01:00
use typstc ::size ::{ Size , Size2D } ;
2019-12-05 20:29:55 +01:00
use typstc ::style ::PageStyle ;
use typstc ::toddle ::query ::FileSystemFontProvider ;
2019-12-08 11:15:04 +01:00
use typstc ::export ::pdf ::PdfExporter ;
2019-10-11 17:53:28 +02:00
2019-12-08 11:15:04 +01:00
type Result < T > = std ::result ::Result < T , Box < dyn Error > > ;
2019-10-13 12:08:07 +02:00
2019-12-08 11:15:04 +01:00
fn main ( ) -> Result < ( ) > {
let opts = Options ::parse ( ) ;
2019-10-11 20:28:22 +02:00
2019-12-08 11:15:04 +01:00
create_dir_all ( " tests/cache/serial " ) ? ;
create_dir_all ( " tests/cache/render " ) ? ;
create_dir_all ( " tests/cache/pdf " ) ? ;
2019-10-11 17:53:28 +02:00
2019-12-08 11:15:04 +01:00
let tests : Vec < _ > = read_dir ( " tests/layouts/ " ) ? . collect ( ) ;
2019-12-09 13:29:04 +01:00
let mut filtered = Vec ::new ( ) ;
2019-10-11 17:53:28 +02:00
2019-12-08 11:15:04 +01:00
for entry in tests {
let path = entry ? . path ( ) ;
if path . extension ( ) ! = Some ( OsStr ::new ( " typ " ) ) {
2019-10-16 21:31:14 +02:00
continue ;
}
2019-12-08 11:15:04 +01:00
let name = path
. file_stem ( ) . ok_or ( " expected file stem " ) ?
2019-12-09 13:29:04 +01:00
. to_string_lossy ( )
. to_string ( ) ;
2019-10-11 20:28:22 +02:00
2019-12-08 11:15:04 +01:00
if opts . matches ( & name ) {
let src = read_to_string ( & path ) ? ;
2019-12-09 13:29:04 +01:00
filtered . push ( ( name , src ) ) ;
2019-10-11 20:28:22 +02:00
}
2019-10-11 17:53:28 +02:00
}
2019-12-06 13:26:44 +01:00
2019-12-09 13:29:04 +01:00
let len = filtered . len ( ) ;
println! ( ) ;
println! ( " Running {} test {} " , len , if len > 1 { " s " } else { " " } ) ;
for ( name , src ) in filtered {
2020-01-04 22:43:26 +01:00
panic ::catch_unwind ( | | {
if let Err ( e ) = test ( & name , & src ) {
println! ( " error: {} " , e ) ;
}
} ) . ok ( ) ;
2019-12-09 13:29:04 +01:00
}
2019-12-07 14:42:25 +01:00
println! ( ) ;
2019-12-08 11:15:04 +01:00
Ok ( ( ) )
2019-10-11 17:53:28 +02:00
}
/// Create a _PDF_ with a name from the source code.
2019-12-08 11:15:04 +01:00
fn test ( name : & str , src : & str ) -> Result < ( ) > {
2019-11-06 23:03:04 +01:00
println! ( " Testing: {} . " , name ) ;
2019-10-16 21:31:14 +02:00
2019-10-11 17:53:28 +02:00
let mut typesetter = Typesetter ::new ( ) ;
2019-11-28 20:38:21 +01:00
typesetter . set_page_style ( PageStyle {
dimensions : Size2D ::with_all ( Size ::pt ( 250.0 ) ) ,
2019-12-15 15:09:09 +01:00
.. PageStyle ::default ( )
2019-11-28 20:38:21 +01:00
} ) ;
2020-01-04 22:43:26 +01:00
let provider = FileSystemFontProvider ::from_index ( " ../fonts/index.json " ) ? ;
2019-12-08 11:15:04 +01:00
let font_paths = provider . paths ( ) ;
typesetter . add_font_provider ( provider ) ;
let layouts = match compile ( & typesetter , src ) {
Some ( layouts ) = > layouts ,
None = > return Ok ( ( ) ) ,
} ;
// Compute the font's paths.
let mut fonts = HashMap ::new ( ) ;
let loader = typesetter . loader ( ) . borrow ( ) ;
for layout in & layouts {
for index in layout . find_used_fonts ( ) {
fonts . entry ( index ) . or_insert_with ( | | {
2020-01-04 22:43:26 +01:00
let p = loader . get_provider_and_index ( index . id ) . 1 ;
& font_paths [ p ] [ index . variant ]
2019-12-08 11:15:04 +01:00
} ) ;
}
}
2019-12-09 13:29:04 +01:00
drop ( loader ) ;
2019-12-08 11:15:04 +01:00
// Write the serialized layout file.
let path = format! ( " tests/cache/serial/ {} " , name ) ;
let mut file = BufWriter ::new ( File ::create ( path ) ? ) ;
2019-10-11 17:53:28 +02:00
2019-12-08 11:15:04 +01:00
// Write the font mapping into the serialization file.
writeln! ( file , " {} " , fonts . len ( ) ) ? ;
for ( index , path ) in fonts . iter ( ) {
2020-01-04 22:43:26 +01:00
writeln! ( file , " {} {} {} " , index . id , index . variant , path ) ? ;
2019-12-08 11:15:04 +01:00
}
layouts . serialize ( & mut file ) ? ;
// Render the layout into a PNG.
Command ::new ( " python " )
. arg ( " tests/render.py " )
. arg ( name )
. spawn ( )
. expect ( " failed to run python renderer " ) ;
// Write the PDF file.
let path = format! ( " tests/cache/pdf/ {} .pdf " , name ) ;
let file = BufWriter ::new ( File ::create ( path ) ? ) ;
let exporter = PdfExporter ::new ( ) ;
exporter . export ( & layouts , typesetter . loader ( ) , file ) ? ;
Ok ( ( ) )
}
/// Compile the source code with the typesetter.
fn compile ( typesetter : & Typesetter , src : & str ) -> Option < MultiLayout > {
2019-11-30 14:10:35 +01:00
#[ cfg(not(debug_assertions)) ] {
2019-11-28 20:38:21 +01:00
use std ::time ::Instant ;
// Warmup.
let warmup_start = Instant ::now ( ) ;
2020-01-06 12:41:42 +01:00
let is_ok = block_on ( typesetter . typeset ( & src ) ) . is_ok ( ) ;
2019-11-28 20:38:21 +01:00
let warmup_end = Instant ::now ( ) ;
2019-10-14 17:32:37 +02:00
2019-12-08 11:15:04 +01:00
// Only continue if the typesetting was successful.
2019-11-30 14:10:35 +01:00
if is_ok {
let start = Instant ::now ( ) ;
let tree = typesetter . parse ( & src ) . unwrap ( ) ;
let mid = Instant ::now ( ) ;
2020-01-06 12:41:42 +01:00
block_on ( typesetter . layout ( & tree ) ) . unwrap ( ) ;
2019-11-30 14:10:35 +01:00
let end = Instant ::now ( ) ;
println! ( " - cold start: {:?} " , warmup_end - warmup_start ) ;
println! ( " - warmed up: {:?} " , end - start ) ;
println! ( " - parsing: {:?} " , mid - start ) ;
println! ( " - layouting: {:?} " , end - mid ) ;
println! ( ) ;
}
} ;
2019-11-28 20:38:21 +01:00
2020-01-06 12:41:42 +01:00
match block_on ( typesetter . typeset ( & src ) ) {
2019-12-08 11:15:04 +01:00
Ok ( layouts ) = > Some ( layouts ) ,
2019-11-30 14:10:35 +01:00
Err ( err ) = > {
println! ( " - compilation failed: {} " , err ) ;
#[ cfg(not(debug_assertions)) ]
println! ( ) ;
2019-12-08 11:15:04 +01:00
None
2019-12-05 20:29:55 +01:00
}
2019-12-08 11:15:04 +01:00
}
}
2019-11-28 20:38:21 +01:00
2019-12-08 11:15:04 +01:00
/// Command line options.
struct Options {
filter : Vec < String > ,
perfect : bool ,
}
2019-10-11 17:53:28 +02:00
2019-12-08 11:15:04 +01:00
impl Options {
/// Parse the options from the environment arguments.
fn parse ( ) -> Options {
let mut perfect = false ;
let mut filter = Vec ::new ( ) ;
for arg in std ::env ::args ( ) . skip ( 1 ) {
match arg . as_str ( ) {
" --nocapture " = > { } ,
" = " = > perfect = true ,
_ = > filter . push ( arg ) ,
2019-10-11 17:53:28 +02:00
}
}
2019-12-08 11:15:04 +01:00
Options { filter , perfect }
2019-10-11 17:53:28 +02:00
}
2019-10-16 21:31:14 +02:00
2019-12-08 11:15:04 +01:00
/// Whether a given test should be executed.
fn matches ( & self , name : & str ) -> bool {
match self . perfect {
true = > self . filter . iter ( ) . any ( | p | name = = p ) ,
false = > self . filter . is_empty ( )
| | self . filter . iter ( ) . any ( | p | name . contains ( p ) )
}
}
2019-10-16 21:31:14 +02:00
}