2020-08-02 22:17:42 +03:00
use std ::cell ::RefCell ;
use std ::env ;
use std ::ffi ::OsStr ;
use std ::fs ::{ self , File } ;
use std ::io ::BufWriter ;
2020-08-17 17:18:55 +03:00
use std ::path ::Path ;
2020-08-02 22:17:42 +03:00
use std ::rc ::Rc ;
2020-08-03 17:01:23 +03:00
use fontdock ::fs ::{ FsIndex , FsProvider } ;
2020-08-02 22:17:42 +03:00
use futures_executor ::block_on ;
2020-08-03 17:01:23 +03:00
use raqote ::{ DrawTarget , PathBuilder , SolidSource , Source , Transform , Vector } ;
2020-08-02 22:17:42 +03:00
use ttf_parser ::OutlineBuilder ;
2020-08-03 17:01:23 +03:00
use typstc ::export ::pdf ;
2020-09-30 14:18:42 +03:00
use typstc ::font ::{ FontLoader , SharedFontLoader } ;
2020-10-04 19:18:55 +03:00
use typstc ::geom ::{ Point , Vec2 } ;
use typstc ::layout ::{ LayoutElement , MultiLayout , Shaped } ;
2020-10-01 12:32:48 +03:00
use typstc ::parse ::LineMap ;
2020-10-04 19:18:55 +03:00
use typstc ::{ typeset , Feedback , Pass } ;
2020-08-02 22:17:42 +03:00
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 ;
}
2020-08-03 17:01:23 +03:00
let name = path . file_stem ( ) . unwrap ( ) . to_string_lossy ( ) . to_string ( ) ;
2020-08-02 22:17:42 +03:00
if filter . matches ( & name ) {
let src = fs ::read_to_string ( & path ) . unwrap ( ) ;
2020-08-17 17:18:55 +03:00
filtered . push ( ( name , path , src ) ) ;
2020-08-02 22:17:42 +03:00
}
}
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 ) ;
2020-09-30 14:18:42 +03:00
let ( descriptors , files ) = index . into_vecs ( ) ;
let provider = FsProvider ::new ( files ) ;
let loader = FontLoader ::new ( Box ::new ( provider ) , descriptors ) ;
2020-08-02 22:17:42 +03:00
let loader = Rc ::new ( RefCell ::new ( loader ) ) ;
2020-08-17 17:18:55 +03:00
for ( name , path , src ) in filtered {
2020-10-04 19:18:55 +03:00
test ( & name , & src , & path , & loader )
2020-08-02 22:17:42 +03:00
}
}
2020-10-04 19:18:55 +03:00
fn test ( name : & str , src : & str , src_path : & Path , loader : & SharedFontLoader ) {
2020-08-02 22:17:42 +03:00
println! ( " Testing {} . " , name ) ;
2020-10-04 19:18:55 +03:00
let style = Default ::default ( ) ;
let scope = typstc ::library ::_std ( ) ;
2020-09-30 18:25:09 +03:00
let Pass {
output : layouts ,
feedback : Feedback { mut diagnostics , .. } ,
2020-10-04 19:18:55 +03:00
} = block_on ( typeset ( & src , & style , & scope , Rc ::clone ( loader ) ) ) ;
2020-09-30 18:25:09 +03:00
if ! diagnostics . is_empty ( ) {
diagnostics . sort ( ) ;
let map = LineMap ::new ( & src ) ;
for diagnostic in diagnostics {
let span = diagnostic . span ;
let start = map . location ( span . start ) ;
let end = map . location ( span . end ) ;
println! (
" {}: {}:{}-{}: {} " ,
diagnostic . v . level ,
src_path . display ( ) ,
start ,
end ,
diagnostic . v . message ,
) ;
}
2020-08-02 22:17:42 +03:00
}
2020-09-30 14:18:42 +03:00
let loader = loader . borrow ( ) ;
2020-08-02 22:17:42 +03:00
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 < String > ,
perfect : bool ,
}
impl TestFilter {
2020-08-03 17:01:23 +03:00
fn new ( args : impl Iterator < Item = String > ) -> Self {
2020-08-02 22:17:42 +03:00
let mut filter = Vec ::new ( ) ;
let mut perfect = false ;
for arg in args {
match arg . as_str ( ) {
2020-08-03 17:01:23 +03:00
" --nocapture " = > { }
2020-08-02 22:17:42 +03:00
" = " = > perfect = true ,
_ = > filter . push ( arg ) ,
}
}
2020-08-03 17:01:23 +03:00
Self { filter , perfect }
2020-08-02 22:17:42 +03:00
}
fn matches ( & self , name : & str ) -> bool {
if self . perfect {
self . filter . iter ( ) . any ( | p | name = = p )
} else {
2020-08-30 23:18:55 +03:00
self . filter . is_empty ( ) | | self . filter . iter ( ) . any ( | p | name . contains ( p ) )
2020-08-02 22:17:42 +03:00
}
}
}
2020-09-30 14:18:42 +03:00
fn render ( layouts : & MultiLayout , loader : & FontLoader , scale : f64 ) -> DrawTarget {
2020-08-02 22:17:42 +03:00
let pad = scale * 10.0 ;
2020-08-30 23:18:55 +03:00
let width = 2.0 * pad
+ layouts
. iter ( )
2020-10-03 14:23:59 +03:00
. map ( | l | scale * l . size . width )
2020-08-30 23:18:55 +03:00
. max_by ( | a , b | a . partial_cmp ( & b ) . unwrap ( ) )
. unwrap ( )
. round ( ) ;
2020-10-03 14:23:59 +03:00
let height = pad
+ layouts
. iter ( )
. map ( | l | scale * l . size . height + pad )
. sum ::< f64 > ( )
. round ( ) ;
2020-08-02 22:17:42 +03:00
let mut surface = DrawTarget ::new ( width as i32 , height as i32 ) ;
surface . clear ( BLACK ) ;
2020-10-03 14:23:59 +03:00
let mut offset = Vec2 ::new ( pad , pad ) ;
2020-08-02 22:17:42 +03:00
for layout in layouts {
surface . fill_rect (
offset . x as f32 ,
offset . y as f32 ,
2020-10-03 14:23:59 +03:00
( scale * layout . size . width ) as f32 ,
( scale * layout . size . height ) as f32 ,
2020-08-02 22:17:42 +03:00
& Source ::Solid ( WHITE ) ,
& Default ::default ( ) ,
) ;
for & ( pos , ref element ) in & layout . elements . 0 {
match element {
2020-08-03 17:01:23 +03:00
LayoutElement ::Text ( shaped ) = > render_shaped (
& mut surface ,
loader ,
shaped ,
2020-10-03 14:23:59 +03:00
( scale * pos . to_vec2 ( ) + offset ) . to_point ( ) ,
2020-08-03 17:01:23 +03:00
scale ,
) ,
2020-08-02 22:17:42 +03:00
}
}
2020-10-03 14:23:59 +03:00
offset . y + = scale * layout . size . height + pad ;
2020-08-02 22:17:42 +03:00
}
surface
}
fn render_shaped (
surface : & mut DrawTarget ,
2020-09-30 14:18:42 +03:00
loader : & FontLoader ,
2020-08-02 22:17:42 +03:00
shaped : & Shaped ,
2020-10-03 14:23:59 +03:00
pos : Point ,
2020-08-02 22:17:42 +03:00
scale : f64 ,
) {
2020-09-30 14:18:42 +03:00
let face = loader . get_loaded ( shaped . face ) . get ( ) ;
2020-08-02 22:17:42 +03:00
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 . size / units_per_em as f64 ) ;
let x = pos . x + scale * offset ;
let y = pos . y + scale * shaped . size ;
let t = Transform ::create_scale ( s as f32 , - s as f32 )
. post_translate ( Vector ::new ( x as f32 , y as f32 ) ) ;
2020-08-03 17:01:23 +03:00
surface . fill (
2020-08-02 22:17:42 +03:00
& 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 ( ) ;
}
}