2020-08-02 22:17:42 +03:00
use std ::cell ::RefCell ;
use std ::env ;
use std ::ffi ::OsStr ;
use std ::fs ::{ self , File } ;
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-10-12 17:59:21 +03:00
use fontdock ::fs ::{ FsIndex , FsSource } ;
2020-11-28 00:35:42 +03:00
use image ::{ DynamicImage , GenericImageView , Rgba } ;
2020-10-13 13:34:11 +03:00
use memmap ::Mmap ;
2020-11-25 20:46:47 +03:00
use tiny_skia ::{
Canvas , Color , ColorU8 , FillRule , FilterQuality , Paint , PathBuilder , Pattern , Pixmap ,
Rect , SpreadMode , Transform ,
} ;
2020-08-02 22:17:42 +03:00
use ttf_parser ::OutlineBuilder ;
2020-10-13 12:47:29 +03:00
use typst ::diag ::{ Feedback , Pass } ;
2020-11-28 00:35:42 +03:00
use typst ::env ::{ Env , ResourceLoader , SharedEnv } ;
2020-10-13 12:47:29 +03:00
use typst ::eval ::State ;
use typst ::export ::pdf ;
2020-11-28 00:35:42 +03:00
use typst ::font ::FontLoader ;
2020-10-13 12:47:29 +03:00
use typst ::geom ::{ Length , Point } ;
2020-11-20 18:36:22 +03:00
use typst ::layout ::{ BoxLayout , ImageElement , LayoutElement } ;
2020-10-13 12:47:29 +03:00
use typst ::parse ::LineMap ;
use typst ::shaping ::Shaped ;
use typst ::typeset ;
2020-08-02 22:17:42 +03:00
2020-11-20 18:36:22 +03:00
const FONT_DIR : & str = " ../fonts " ;
const TYP_DIR : & str = " typ " ;
const PDF_DIR : & str = " pdf " ;
const PNG_DIR : & str = " png " ;
const REF_DIR : & str = " ref " ;
2020-08-02 22:17:42 +03:00
fn main ( ) {
2020-11-20 18:36:22 +03:00
env ::set_current_dir ( env ::current_dir ( ) . unwrap ( ) . join ( " tests " ) ) . unwrap ( ) ;
2020-08-02 22:17:42 +03:00
let filter = TestFilter ::new ( env ::args ( ) . skip ( 1 ) ) ;
let mut filtered = Vec ::new ( ) ;
2020-10-13 13:34:11 +03:00
for entry in fs ::read_dir ( TYP_DIR ) . unwrap ( ) {
let src_path = entry . unwrap ( ) . path ( ) ;
if src_path . extension ( ) ! = Some ( OsStr ::new ( " typ " ) ) {
2020-08-02 22:17:42 +03:00
continue ;
}
2020-10-13 13:34:11 +03:00
let name = src_path . file_stem ( ) . unwrap ( ) . to_string_lossy ( ) . to_string ( ) ;
let pdf_path = Path ::new ( PDF_DIR ) . join ( & name ) . with_extension ( " pdf " ) ;
let png_path = Path ::new ( PNG_DIR ) . join ( & name ) . with_extension ( " png " ) ;
let ref_path = Path ::new ( REF_DIR ) . join ( & name ) . with_extension ( " png " ) ;
2020-08-02 22:17:42 +03:00
if filter . matches ( & name ) {
2020-10-13 13:34:11 +03:00
filtered . push ( ( name , src_path , pdf_path , png_path , ref_path ) ) ;
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 ) ;
}
2020-10-13 13:34:11 +03:00
fs ::create_dir_all ( PDF_DIR ) . unwrap ( ) ;
fs ::create_dir_all ( PNG_DIR ) . unwrap ( ) ;
2020-08-02 22:17:42 +03:00
let mut index = FsIndex ::new ( ) ;
index . search_dir ( FONT_DIR ) ;
2020-10-12 17:59:21 +03:00
let ( files , descriptors ) = index . into_vecs ( ) ;
2020-11-28 00:35:42 +03:00
let env = Rc ::new ( RefCell ::new ( Env {
fonts : FontLoader ::new ( Box ::new ( FsSource ::new ( files ) ) , descriptors ) ,
resources : ResourceLoader ::new ( ) ,
} ) ) ;
2020-08-02 22:17:42 +03:00
2020-10-13 13:34:11 +03:00
let mut ok = true ;
for ( name , src_path , pdf_path , png_path , ref_path ) in filtered {
print! ( " Testing {} . " , name ) ;
2020-11-28 00:35:42 +03:00
test ( & src_path , & pdf_path , & png_path , & env ) ;
2020-10-13 13:34:11 +03:00
let png_file = File ::open ( & png_path ) . unwrap ( ) ;
let ref_file = match File ::open ( & ref_path ) {
Ok ( file ) = > file ,
Err ( _ ) = > {
println! ( " Failed to open reference image. ❌ " ) ;
ok = false ;
continue ;
}
} ;
let a = unsafe { Mmap ::map ( & png_file ) . unwrap ( ) } ;
let b = unsafe { Mmap ::map ( & ref_file ) . unwrap ( ) } ;
if * a ! = * b {
println! ( " Does not match reference image. ❌ " ) ;
ok = false ;
} else {
println! ( " Okay. ✔ " ) ;
}
2020-08-02 22:17:42 +03:00
}
2020-10-13 13:34:11 +03:00
if ! ok {
std ::process ::exit ( 1 ) ;
}
}
2020-08-02 22:17:42 +03:00
2020-11-28 00:35:42 +03:00
fn test ( src_path : & Path , pdf_path : & Path , png_path : & Path , env : & SharedEnv ) {
2020-10-13 13:34:11 +03:00
let src = fs ::read_to_string ( src_path ) . unwrap ( ) ;
2020-10-04 20:06:20 +03:00
let state = State ::default ( ) ;
2020-09-30 18:25:09 +03:00
let Pass {
output : layouts ,
2020-10-04 21:22:11 +03:00
feedback : Feedback { mut diags , .. } ,
2020-11-28 00:35:42 +03:00
} = typeset ( & src , Rc ::clone ( env ) , state ) ;
2020-09-30 18:25:09 +03:00
2020-10-04 21:22:11 +03:00
if ! diags . is_empty ( ) {
diags . sort ( ) ;
2020-09-30 18:25:09 +03:00
let map = LineMap ::new ( & src ) ;
2020-10-04 21:22:11 +03:00
for diag in diags {
let span = diag . span ;
2020-09-30 18:25:09 +03:00
let start = map . location ( span . start ) ;
let end = map . location ( span . end ) ;
println! (
" {}: {}:{}-{}: {} " ,
2020-10-04 21:22:11 +03:00
diag . v . level ,
2020-09-30 18:25:09 +03:00
src_path . display ( ) ,
start ,
end ,
2020-10-04 21:22:11 +03:00
diag . v . message ,
2020-09-30 18:25:09 +03:00
) ;
}
2020-08-02 22:17:42 +03:00
}
2020-11-28 00:35:42 +03:00
let env = env . borrow ( ) ;
let canvas = draw ( & layouts , & env , 2.0 ) ;
2020-11-25 20:46:47 +03:00
canvas . pixmap . save_png ( png_path ) . unwrap ( ) ;
2020-08-02 22:17:42 +03:00
2020-11-28 00:35:42 +03:00
let pdf_data = pdf ::export ( & layouts , & env ) ;
2020-11-20 01:46:51 +03:00
fs ::write ( pdf_path , pdf_data ) . unwrap ( ) ;
2020-08-02 22:17:42 +03:00
}
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-11-28 00:35:42 +03:00
fn draw ( layouts : & [ BoxLayout ] , env : & Env , pixel_per_pt : f32 ) -> Canvas {
2020-11-25 20:46:47 +03:00
let pad = Length ::pt ( 5.0 ) ;
let height = pad + layouts . iter ( ) . map ( | l | l . size . height + pad ) . sum ::< Length > ( ) ;
2020-08-30 23:18:55 +03:00
let width = 2.0 * pad
+ layouts
. iter ( )
2020-11-25 20:46:47 +03:00
. map ( | l | l . size . width )
2020-08-30 23:18:55 +03:00
. max_by ( | a , b | a . partial_cmp ( & b ) . unwrap ( ) )
2020-10-10 23:19:36 +03:00
. unwrap ( ) ;
2020-08-30 23:18:55 +03:00
2020-11-25 20:46:47 +03:00
let pixel_width = ( pixel_per_pt * width . to_pt ( ) as f32 ) as u32 ;
let pixel_height = ( pixel_per_pt * height . to_pt ( ) as f32 ) as u32 ;
let mut canvas = Canvas ::new ( pixel_width , pixel_height ) . unwrap ( ) ;
canvas . scale ( pixel_per_pt , pixel_per_pt ) ;
canvas . pixmap . fill ( Color ::BLACK ) ;
2020-08-02 22:17:42 +03:00
2020-11-25 20:46:47 +03:00
let mut origin = Point ::new ( pad , pad ) ;
2020-08-02 22:17:42 +03:00
for layout in layouts {
2020-11-25 20:46:47 +03:00
let mut paint = Paint ::default ( ) ;
paint . set_color ( Color ::WHITE ) ;
canvas . fill_rect (
Rect ::from_xywh (
origin . x . to_pt ( ) as f32 ,
origin . y . to_pt ( ) as f32 ,
layout . size . width . to_pt ( ) as f32 ,
layout . size . height . to_pt ( ) as f32 ,
)
. unwrap ( ) ,
& paint ,
2020-08-02 22:17:42 +03:00
) ;
2020-10-10 23:19:36 +03:00
for & ( pos , ref element ) in & layout . elements {
2020-11-25 20:46:47 +03:00
let pos = origin + pos ;
2020-08-02 22:17:42 +03:00
match element {
2020-11-20 18:36:22 +03:00
LayoutElement ::Text ( shaped ) = > {
2020-11-28 00:35:42 +03:00
draw_text ( & mut canvas , pos , env , shaped ) ;
}
LayoutElement ::Image ( image ) = > {
draw_image ( & mut canvas , pos , env , image ) ;
2020-11-20 18:36:22 +03:00
}
2020-08-02 22:17:42 +03:00
}
}
2020-11-25 20:46:47 +03:00
origin . y + = layout . size . height + pad ;
2020-08-02 22:17:42 +03:00
}
2020-11-25 20:46:47 +03:00
canvas
2020-08-02 22:17:42 +03:00
}
2020-11-28 00:35:42 +03:00
fn draw_text ( canvas : & mut Canvas , pos : Point , env : & Env , shaped : & Shaped ) {
let face = env . fonts . get_loaded ( shaped . face ) . get ( ) ;
2020-08-02 22:17:42 +03:00
for ( & glyph , & offset ) in shaped . glyphs . iter ( ) . zip ( & shaped . offsets ) {
2020-11-25 20:46:47 +03:00
let units_per_em = face . units_per_em ( ) . unwrap_or ( 1000 ) ;
let x = ( pos . x + offset ) . to_pt ( ) as f32 ;
let y = ( pos . y + shaped . font_size ) . to_pt ( ) as f32 ;
let scale = ( shaped . font_size / units_per_em as f64 ) . to_pt ( ) as f32 ;
2020-08-02 22:17:42 +03:00
let mut builder = WrappedPathBuilder ( PathBuilder ::new ( ) ) ;
face . outline_glyph ( glyph , & mut builder ) ;
2020-11-25 20:46:47 +03:00
let path = builder . 0. finish ( ) . unwrap ( ) ;
let placed = path
. transform ( & Transform ::from_row ( scale , 0.0 , 0.0 , - scale , x , y ) . unwrap ( ) )
. unwrap ( ) ;
let mut paint = Paint ::default ( ) ;
paint . anti_alias = true ;
canvas . fill_path ( & placed , & paint , FillRule ::default ( ) ) ;
2020-08-02 22:17:42 +03:00
}
}
2020-11-28 00:35:42 +03:00
fn draw_image ( canvas : & mut Canvas , pos : Point , env : & Env , image : & ImageElement ) {
2020-11-28 02:04:40 +03:00
let buf = env . resources . get_loaded ::< DynamicImage > ( image . res ) ;
2020-11-28 00:35:42 +03:00
let mut pixmap = Pixmap ::new ( buf . width ( ) , buf . height ( ) ) . unwrap ( ) ;
for ( ( _ , _ , src ) , dest ) in buf . pixels ( ) . zip ( pixmap . pixels_mut ( ) ) {
let Rgba ( [ r , g , b , a ] ) = src ;
* dest = ColorU8 ::from_rgba ( r , g , b , a ) . premultiply ( ) ;
2020-11-20 18:36:22 +03:00
}
2020-11-25 20:46:47 +03:00
let view_width = image . size . width . to_pt ( ) as f32 ;
let view_height = image . size . height . to_pt ( ) as f32 ;
let x = pos . x . to_pt ( ) as f32 ;
let y = pos . y . to_pt ( ) as f32 ;
let scale_x = view_width as f32 / pixmap . width ( ) as f32 ;
let scale_y = view_height as f32 / pixmap . height ( ) as f32 ;
let mut paint = Paint ::default ( ) ;
paint . shader = Pattern ::new (
& pixmap ,
SpreadMode ::Pad ,
FilterQuality ::Bilinear ,
1.0 ,
Transform ::from_row ( scale_x , 0.0 , 0.0 , scale_y , x , y ) . unwrap ( ) ,
) ;
canvas . fill_rect (
Rect ::from_xywh ( x , y , view_width , view_height ) . unwrap ( ) ,
& paint ,
2020-11-20 18:36:22 +03:00
) ;
}
2020-08-02 22:17:42 +03:00
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 ( ) ;
}
}