2020-08-02 22:17:42 +03:00
use std ::env ;
use std ::ffi ::OsStr ;
2022-01-01 14:12:50 +03:00
use std ::fs ;
2022-01-04 01:18:21 +03:00
use std ::ops ::Range ;
2020-08-17 17:18:55 +03:00
use std ::path ::Path ;
2022-01-31 18:06:44 +03:00
use std ::sync ::Arc ;
2020-08-02 22:17:42 +03:00
2021-07-08 23:33:44 +03:00
use tiny_skia as sk ;
2021-01-14 18:47:29 +03:00
use walkdir ::WalkDir ;
2020-08-02 22:17:42 +03:00
2021-08-17 23:04:18 +03:00
use typst ::diag ::Error ;
2021-12-30 14:12:19 +03:00
use typst ::eval ::{ Smart , StyleMap , Value } ;
2022-01-24 18:48:24 +03:00
use typst ::frame ::{ Element , Frame } ;
2022-02-08 23:12:09 +03:00
use typst ::geom ::{ Length , RgbaColor } ;
2021-12-20 16:18:29 +03:00
use typst ::library ::{ PageNode , TextNode } ;
2021-08-09 12:06:37 +03:00
use typst ::loading ::FsLoader ;
2021-08-17 23:04:18 +03:00
use typst ::parse ::Scanner ;
2022-01-04 01:18:21 +03:00
use typst ::source ::SourceFile ;
use typst ::syntax ::Span ;
2021-07-20 21:21:56 +03:00
use typst ::Context ;
2020-08-02 22:17:42 +03:00
2022-01-01 14:12:50 +03:00
#[ cfg(feature = " layout-cache " ) ]
use {
filedescriptor ::{ FileDescriptor , StdioDescriptor ::* } ,
std ::fs ::File ,
typst ::layout ::RootNode ,
} ;
2021-02-20 19:53:40 +03:00
const TYP_DIR : & str = " ./typ " ;
const REF_DIR : & str = " ./ref " ;
const PNG_DIR : & str = " ./png " ;
const PDF_DIR : & str = " ./pdf " ;
2020-12-11 00:44:35 +03:00
const FONT_DIR : & str = " ../fonts " ;
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 ( ) ;
2021-03-13 16:18:31 +03:00
let args = Args ::new ( env ::args ( ) . skip ( 1 ) ) ;
2020-08-02 22:17:42 +03:00
let mut filtered = Vec ::new ( ) ;
2022-01-01 14:12:50 +03:00
// Since differents tests can affect each other through the layout cache, a
// deterministic order is very important for reproducibility.
for entry in WalkDir ::new ( " . " ) . sort_by_file_name ( ) {
2021-01-14 18:47:29 +03:00
let entry = entry . unwrap ( ) ;
2021-01-16 17:28:03 +03:00
if entry . depth ( ) < = 1 {
continue ;
}
2021-01-14 18:47:29 +03:00
let src_path = entry . into_path ( ) ;
2020-10-13 13:34:11 +03:00
if src_path . extension ( ) ! = Some ( OsStr ::new ( " typ " ) ) {
2020-08-02 22:17:42 +03:00
continue ;
}
2021-12-15 13:12:38 +03:00
if args . matches ( & src_path ) {
2021-01-14 18:47:29 +03:00
filtered . push ( src_path ) ;
2020-08-02 22:17:42 +03:00
}
}
let len = filtered . len ( ) ;
2021-01-13 23:33:22 +03:00
if len = = 1 {
2020-08-02 22:17:42 +03:00
println! ( " Running test ... " ) ;
2021-01-13 23:33:22 +03:00
} else if len > 1 {
2022-01-13 20:43:18 +03:00
println! ( " Running {len} tests " ) ;
2020-08-02 22:17:42 +03:00
}
2021-12-07 18:36:39 +03:00
// Set page width to 120pt with 10pt margins, so that the inner page is
// exactly 100pt wide. Page height is unbounded and font size is 10pt so
// that it multiplies to nice round numbers.
2021-12-30 14:12:19 +03:00
let mut styles = StyleMap ::new ( ) ;
2021-12-07 18:36:39 +03:00
styles . set ( PageNode ::WIDTH , Smart ::Custom ( Length ::pt ( 120.0 ) ) ) ;
styles . set ( PageNode ::HEIGHT , Smart ::Auto ) ;
styles . set ( PageNode ::LEFT , Smart ::Custom ( Length ::pt ( 10.0 ) . into ( ) ) ) ;
styles . set ( PageNode ::TOP , Smart ::Custom ( Length ::pt ( 10.0 ) . into ( ) ) ) ;
styles . set ( PageNode ::RIGHT , Smart ::Custom ( Length ::pt ( 10.0 ) . into ( ) ) ) ;
styles . set ( PageNode ::BOTTOM , Smart ::Custom ( Length ::pt ( 10.0 ) . into ( ) ) ) ;
2021-12-15 13:11:57 +03:00
styles . set ( TextNode ::SIZE , Length ::pt ( 10.0 ) . into ( ) ) ;
2021-07-20 21:21:56 +03:00
2021-07-30 19:04:08 +03:00
// Hook up an assert function into the global scope.
2021-07-21 12:25:49 +03:00
let mut std = typst ::library ::new ( ) ;
2022-02-08 23:12:09 +03:00
std . def_const ( " conifer " , RgbaColor ::new ( 0x9f , 0xEB , 0x52 , 0xFF ) ) ;
std . def_const ( " forest " , RgbaColor ::new ( 0x43 , 0xA1 , 0x27 , 0xFF ) ) ;
2021-07-30 19:04:08 +03:00
std . def_func ( " test " , move | _ , args | {
let lhs = args . expect ::< Value > ( " left-hand side " ) ? ;
let rhs = args . expect ::< Value > ( " right-hand side " ) ? ;
if lhs ! = rhs {
2021-08-12 14:39:33 +03:00
return Err ( Error ::boxed (
2021-07-30 19:04:08 +03:00
args . span ,
2021-08-12 14:39:33 +03:00
format! ( " Assertion failed: {:?} != {:?} " , lhs , rhs ) ,
) ) ;
2021-07-30 19:04:08 +03:00
}
Ok ( Value ::None )
} ) ;
2020-08-02 22:17:42 +03:00
2021-07-21 12:25:49 +03:00
// Create loader and context.
let loader = FsLoader ::new ( ) . with_path ( FONT_DIR ) . wrap ( ) ;
2021-12-07 18:36:39 +03:00
let mut ctx = Context ::builder ( ) . std ( std ) . styles ( styles ) . build ( loader ) ;
2020-08-02 22:17:42 +03:00
2021-07-21 12:25:49 +03:00
// Run all the tests.
2021-12-15 13:11:57 +03:00
let mut ok = 0 ;
2021-01-14 18:47:29 +03:00
for src_path in filtered {
2021-03-13 16:18:31 +03:00
let path = src_path . strip_prefix ( TYP_DIR ) . unwrap ( ) ;
let png_path = Path ::new ( PNG_DIR ) . join ( path ) . with_extension ( " png " ) ;
let ref_path = Path ::new ( REF_DIR ) . join ( path ) . with_extension ( " png " ) ;
let pdf_path =
args . pdf . then ( | | Path ::new ( PDF_DIR ) . join ( path ) . with_extension ( " pdf " ) ) ;
2021-12-15 13:11:57 +03:00
ok + = test (
2021-07-20 21:21:56 +03:00
& mut ctx ,
2021-03-13 16:18:31 +03:00
& src_path ,
& png_path ,
& ref_path ,
pdf_path . as_deref ( ) ,
2021-12-15 13:12:38 +03:00
args . debug ,
2021-12-15 13:11:57 +03:00
) as usize ;
}
if len > 1 {
2022-01-13 20:43:18 +03:00
println! ( " {ok} / {len} tests passed. " ) ;
2021-01-13 16:07:38 +03:00
}
2020-10-13 13:34:11 +03:00
2021-12-15 13:11:57 +03:00
if ok < len {
2020-10-13 13:34:11 +03:00
std ::process ::exit ( 1 ) ;
}
}
2020-08-02 22:17:42 +03:00
2021-03-13 16:18:31 +03:00
struct Args {
2020-08-02 22:17:42 +03:00
filter : Vec < String > ,
2021-12-15 13:12:38 +03:00
exact : bool ,
debug : bool ,
2021-03-13 16:18:31 +03:00
pdf : bool ,
2020-08-02 22:17:42 +03:00
}
2021-03-13 16:18:31 +03:00
impl Args {
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 ( ) ;
2021-12-15 13:12:38 +03:00
let mut exact = false ;
let mut debug = false ;
2021-03-13 16:18:31 +03:00
let mut pdf = false ;
2020-08-02 22:17:42 +03:00
for arg in args {
match arg . as_str ( ) {
2021-12-15 13:12:38 +03:00
// Ignore this, its for cargo.
2020-08-03 17:01:23 +03:00
" --nocapture " = > { }
2021-12-15 13:12:38 +03:00
// Match only the exact filename.
" --exact " = > exact = true ,
// Generate PDFs.
2021-03-13 16:18:31 +03:00
" --pdf " = > pdf = true ,
2021-12-15 13:12:38 +03:00
// Debug print the layout trees.
" --debug " | " -d " = > debug = true ,
// Everything else is a file filter.
2020-08-02 22:17:42 +03:00
_ = > filter . push ( arg ) ,
}
}
2021-12-15 13:12:38 +03:00
Self { filter , pdf , debug , exact }
2020-08-02 22:17:42 +03:00
}
2021-12-15 13:12:38 +03:00
fn matches ( & self , path : & Path ) -> bool {
if self . exact {
let name = path . file_name ( ) . unwrap ( ) . to_string_lossy ( ) ;
self . filter . iter ( ) . any ( | v | v = = & name )
2020-08-02 22:17:42 +03:00
} else {
2021-12-15 13:12:38 +03:00
let path = path . to_string_lossy ( ) ;
self . filter . is_empty ( ) | | self . filter . iter ( ) . any ( | v | path . contains ( v ) )
2020-08-02 22:17:42 +03:00
}
}
}
2020-12-11 00:44:35 +03:00
fn test (
2021-07-20 21:21:56 +03:00
ctx : & mut Context ,
2020-12-11 00:44:35 +03:00
src_path : & Path ,
png_path : & Path ,
2021-02-20 19:53:40 +03:00
ref_path : & Path ,
2021-03-13 16:18:31 +03:00
pdf_path : Option < & Path > ,
2021-12-15 13:12:38 +03:00
debug : bool ,
2020-12-11 00:44:35 +03:00
) -> bool {
2021-01-14 18:47:29 +03:00
let name = src_path . strip_prefix ( TYP_DIR ) . unwrap_or ( src_path ) ;
println! ( " Testing {} " , name . display ( ) ) ;
2020-12-11 00:44:35 +03:00
let src = fs ::read_to_string ( src_path ) . unwrap ( ) ;
2021-01-13 16:07:38 +03:00
let mut ok = true ;
let mut frames = vec! [ ] ;
2021-07-31 23:59:14 +03:00
let mut line = 0 ;
2021-02-01 00:43:11 +03:00
let mut compare_ref = true ;
2021-03-12 15:57:47 +03:00
let mut compare_ever = false ;
2022-01-02 02:46:19 +03:00
let mut rng = LinearShift ::new ( ) ;
2021-02-01 00:43:11 +03:00
2021-06-29 17:05:05 +03:00
let parts : Vec < _ > = src . split ( " \n --- " ) . collect ( ) ;
2021-07-31 23:59:14 +03:00
for ( i , & part ) in parts . iter ( ) . enumerate ( ) {
2021-02-01 00:43:11 +03:00
let is_header = i = = 0
& & parts . len ( ) > 1
& & part
. lines ( )
. all ( | s | s . starts_with ( " // " ) | | s . chars ( ) . all ( | c | c . is_whitespace ( ) ) ) ;
if is_header {
for line in part . lines ( ) {
if line . starts_with ( " // Ref: false " ) {
compare_ref = false ;
}
}
} else {
2022-01-02 02:46:19 +03:00
let ( part_ok , compare_here , part_frames ) = test_part (
ctx ,
src_path ,
part . into ( ) ,
i ,
compare_ref ,
line ,
debug ,
& mut rng ,
) ;
2021-02-01 00:43:11 +03:00
ok & = part_ok ;
2021-03-12 15:57:47 +03:00
compare_ever | = compare_here ;
2021-02-01 00:43:11 +03:00
frames . extend ( part_frames ) ;
}
2021-07-31 23:59:14 +03:00
line + = part . lines ( ) . count ( ) + 1 ;
2021-01-13 16:07:38 +03:00
}
2021-03-12 15:57:47 +03:00
if compare_ever {
2021-03-13 16:18:31 +03:00
if let Some ( pdf_path ) = pdf_path {
2021-07-20 21:21:56 +03:00
let pdf_data = typst ::export ::pdf ( ctx , & frames ) ;
2021-03-13 16:18:31 +03:00
fs ::create_dir_all ( & pdf_path . parent ( ) . unwrap ( ) ) . unwrap ( ) ;
fs ::write ( pdf_path , pdf_data ) . unwrap ( ) ;
}
2021-01-13 16:07:38 +03:00
2022-01-24 18:48:24 +03:00
let canvas = render ( ctx , & frames ) ;
2021-01-14 18:47:29 +03:00
fs ::create_dir_all ( & png_path . parent ( ) . unwrap ( ) ) . unwrap ( ) ;
2021-03-23 15:17:00 +03:00
canvas . save_png ( png_path ) . unwrap ( ) ;
2021-01-14 01:19:44 +03:00
2021-07-08 23:33:44 +03:00
if let Ok ( ref_pixmap ) = sk ::Pixmap ::load_png ( ref_path ) {
2021-03-23 15:17:00 +03:00
if canvas ! = ref_pixmap {
2021-02-20 19:53:40 +03:00
println! ( " Does not match reference image. ❌ " ) ;
2021-01-13 16:07:38 +03:00
ok = false ;
}
2021-07-30 19:04:08 +03:00
} else if ! frames . is_empty ( ) {
2021-02-20 19:53:40 +03:00
println! ( " Failed to open reference image. ❌ " ) ;
ok = false ;
2021-01-13 16:07:38 +03:00
}
}
if ok {
2021-12-15 13:12:38 +03:00
if ! debug {
print! ( " \x1b [1A " ) ;
}
println! ( " Testing {} ✔ " , name . display ( ) ) ;
2021-01-13 16:07:38 +03:00
}
ok
}
2021-02-01 00:43:11 +03:00
fn test_part (
2021-07-20 21:21:56 +03:00
ctx : & mut Context ,
2021-08-09 12:06:37 +03:00
src_path : & Path ,
src : String ,
2021-02-01 00:43:11 +03:00
i : usize ,
compare_ref : bool ,
2021-07-31 23:59:14 +03:00
line : usize ,
2021-12-15 13:12:38 +03:00
debug : bool ,
2022-01-02 02:46:19 +03:00
rng : & mut LinearShift ,
2022-01-31 18:06:44 +03:00
) -> ( bool , bool , Vec < Arc < Frame > > ) {
2022-01-30 14:50:58 +03:00
let mut ok = true ;
2021-08-09 12:06:37 +03:00
let id = ctx . sources . provide ( src_path , src ) ;
let source = ctx . sources . get ( id ) ;
2022-01-30 13:16:57 +03:00
if debug {
println! ( " Syntax: {:#?} " , source . root ( ) )
}
2021-08-09 12:06:37 +03:00
2021-07-31 23:59:14 +03:00
let ( local_compare_ref , mut ref_errors ) = parse_metadata ( & source ) ;
2021-02-01 00:43:11 +03:00
let compare_ref = local_compare_ref . unwrap_or ( compare_ref ) ;
2022-01-30 14:50:58 +03:00
ok & = test_reparse ( ctx . sources . get ( id ) . src ( ) , i , rng ) ;
2020-12-11 00:44:35 +03:00
2021-12-20 16:18:29 +03:00
let ( frames , mut errors ) = match ctx . evaluate ( id ) {
Ok ( module ) = > {
let tree = module . into_root ( ) ;
2021-12-15 13:12:38 +03:00
if debug {
2022-01-30 13:16:57 +03:00
println! ( " Layout: {tree:#?} " ) ;
2021-12-15 13:12:38 +03:00
}
2021-12-20 16:18:29 +03:00
let mut frames = tree . layout ( ctx ) ;
2021-06-18 14:00:36 +03:00
2021-07-30 19:04:08 +03:00
#[ cfg(feature = " layout-cache " ) ]
2021-12-20 16:18:29 +03:00
( ok & = test_incremental ( ctx , i , & tree , & frames ) ) ;
2021-06-27 19:06:39 +03:00
2021-07-30 19:04:08 +03:00
if ! compare_ref {
frames . clear ( ) ;
}
2021-07-26 01:08:08 +03:00
2021-07-30 19:04:08 +03:00
( frames , vec! [ ] )
2021-01-26 22:49:45 +03:00
}
2021-07-30 19:04:08 +03:00
Err ( errors ) = > ( vec! [ ] , * errors ) ,
} ;
2021-01-17 15:53:22 +03:00
2021-07-30 19:04:08 +03:00
// TODO: Also handle errors from other files.
2021-08-13 13:21:14 +03:00
errors . retain ( | error | error . span . source = = id ) ;
2021-07-31 23:59:14 +03:00
for error in & mut errors {
error . trace . clear ( ) ;
}
2021-08-21 17:38:51 +03:00
// The comparison never fails since all spans are from the same source file.
ref_errors . sort_by ( | a , b | a . span . partial_cmp ( & b . span ) . unwrap ( ) ) ;
errors . sort_by ( | a , b | a . span . partial_cmp ( & b . span ) . unwrap ( ) ) ;
2021-07-21 12:25:49 +03:00
2021-07-30 19:04:08 +03:00
if errors ! = ref_errors {
2022-01-13 20:43:18 +03:00
println! ( " Subtest {i} does not match expected errors. ❌ " ) ;
2020-12-11 00:44:35 +03:00
ok = false ;
2021-08-09 12:06:37 +03:00
let source = ctx . sources . get ( id ) ;
2021-07-30 19:04:08 +03:00
for error in errors . iter ( ) {
2021-08-13 13:21:14 +03:00
if error . span . source = = id & & ! ref_errors . contains ( error ) {
2021-01-14 19:41:13 +03:00
print! ( " Not annotated | " ) ;
2021-07-31 23:59:14 +03:00
print_error ( & source , line , error ) ;
2020-12-11 00:44:35 +03:00
}
}
2021-07-30 19:04:08 +03:00
for error in ref_errors . iter ( ) {
if ! errors . contains ( error ) {
2021-01-14 19:41:13 +03:00
print! ( " Not emitted | " ) ;
2021-07-31 23:59:14 +03:00
print_error ( & source , line , error ) ;
2020-12-11 00:44:35 +03:00
}
}
}
2021-07-26 01:08:08 +03:00
( ok , compare_ref , frames )
}
2021-06-27 13:28:40 +03:00
2022-01-24 18:48:24 +03:00
fn parse_metadata ( source : & SourceFile ) -> ( Option < bool > , Vec < Error > ) {
let mut compare_ref = None ;
let mut errors = vec! [ ] ;
2021-06-27 13:28:40 +03:00
2022-01-24 18:48:24 +03:00
let lines : Vec < _ > = source . src ( ) . lines ( ) . map ( str ::trim ) . collect ( ) ;
for ( i , line ) in lines . iter ( ) . enumerate ( ) {
if line . starts_with ( " // Ref: false " ) {
compare_ref = Some ( false ) ;
2021-06-27 13:28:40 +03:00
}
2022-01-24 18:48:24 +03:00
if line . starts_with ( " // Ref: true " ) {
compare_ref = Some ( true ) ;
}
2021-06-27 13:28:40 +03:00
2022-01-24 18:48:24 +03:00
let rest = if let Some ( rest ) = line . strip_prefix ( " // Error: " ) {
rest
} else {
continue ;
} ;
2021-07-26 01:08:08 +03:00
2022-01-24 18:48:24 +03:00
fn num ( s : & mut Scanner ) -> usize {
s . eat_while ( | c | c . is_numeric ( ) ) . parse ( ) . unwrap ( )
2021-07-26 01:08:08 +03:00
}
2022-01-24 18:48:24 +03:00
let comments =
lines [ i .. ] . iter ( ) . take_while ( | line | line . starts_with ( " // " ) ) . count ( ) ;
let pos = | s : & mut Scanner | -> usize {
let first = num ( s ) - 1 ;
let ( delta , column ) =
if s . eat_if ( ':' ) { ( first , num ( s ) - 1 ) } else { ( 0 , first ) } ;
let line = ( i + comments ) + delta ;
source . line_column_to_byte ( line , column ) . unwrap ( )
} ;
let mut s = Scanner ::new ( rest ) ;
let start = pos ( & mut s ) ;
let end = if s . eat_if ( '-' ) { pos ( & mut s ) } else { start } ;
let span = Span ::new ( source . id ( ) , start , end ) ;
errors . push ( Error ::new ( span , s . rest ( ) . trim ( ) ) ) ;
2021-06-27 19:06:39 +03:00
}
2022-01-24 18:48:24 +03:00
( compare_ref , errors )
}
2021-07-26 01:08:08 +03:00
2022-01-24 18:48:24 +03:00
fn print_error ( source : & SourceFile , line : usize , error : & Error ) {
let start_line = 1 + line + source . byte_to_line ( error . span . start ) . unwrap ( ) ;
let start_col = 1 + source . byte_to_column ( error . span . start ) . unwrap ( ) ;
let end_line = 1 + line + source . byte_to_line ( error . span . end ) . unwrap ( ) ;
let end_col = 1 + source . byte_to_column ( error . span . end ) . unwrap ( ) ;
println! (
" Error: {start_line}:{start_col}-{end_line}:{end_col}: {} " ,
error . message ,
) ;
2020-12-11 00:44:35 +03:00
}
2022-01-02 02:46:19 +03:00
/// Pseudorandomly edit the source file and test whether a reparse produces the
/// same result as a clean parse.
///
/// The method will first inject 10 strings once every 400 source characters
/// and then select 5 leaf node boundries to inject an additional, randomly
/// chosen string from the injection list.
fn test_reparse ( src : & str , i : usize , rng : & mut LinearShift ) -> bool {
let supplements = [
" [ " ,
2022-01-31 15:26:40 +03:00
" ] " ,
" { " ,
" } " ,
" ( " ,
2022-01-02 02:46:19 +03:00
" ) " ,
" #rect() " ,
" a word " ,
" , a: 1 " ,
" 10.0 " ,
" : " ,
" if i == 0 {true} " ,
" for " ,
" * hello * " ,
" // " ,
" /* " ,
" \\ u{12e4} " ,
" ```typst " ,
" " ,
" trees " ,
" \\ " ,
" $ a $ " ,
" 2. " ,
" - " ,
" 5 " ,
] ;
let mut ok = true ;
let apply = | replace : std ::ops ::Range < usize > , with | {
let mut incr_source = SourceFile ::detached ( src ) ;
2022-01-02 16:46:08 +03:00
if incr_source . root ( ) . len ( ) ! = src . len ( ) {
println! (
2022-01-13 20:43:18 +03:00
" Subtest {i} tree length {} does not match string length {} ❌ " ,
2022-01-02 16:46:08 +03:00
incr_source . root ( ) . len ( ) ,
src . len ( ) ,
) ;
return false ;
}
2022-01-02 02:46:19 +03:00
incr_source . edit ( replace . clone ( ) , with ) ;
2022-01-24 18:48:24 +03:00
let edited_src = incr_source . src ( ) ;
2022-01-02 02:46:19 +03:00
let ref_source = SourceFile ::detached ( edited_src ) ;
let incr_root = incr_source . root ( ) ;
let ref_root = ref_source . root ( ) ;
2022-01-24 18:48:24 +03:00
let same = incr_root = = ref_root ;
if ! same {
2022-01-02 02:46:19 +03:00
println! (
2022-01-13 20:43:18 +03:00
" Subtest {i} reparse differs from clean parse when inserting '{with}' at {}-{} ❌ \n " ,
replace . start , replace . end ,
2022-01-02 02:46:19 +03:00
) ;
2022-01-13 20:43:18 +03:00
println! ( " Expected reference tree: \n {ref_root:#?} \n " ) ;
println! ( " Found incremental tree: \n {incr_root:#?} " ) ;
println! ( " Full source ( {} ): \n \" {edited_src:?} \" " , edited_src . len ( ) ) ;
2022-01-02 02:46:19 +03:00
}
2022-01-24 18:48:24 +03:00
same
2022-01-02 02:46:19 +03:00
} ;
2022-01-04 01:18:21 +03:00
let mut pick = | range : Range < usize > | {
let ratio = rng . next ( ) ;
( range . start as f64 + ratio * ( range . end - range . start ) as f64 ) . floor ( ) as usize
2022-01-02 02:46:19 +03:00
} ;
let insertions = ( src . len ( ) as f64 / 400.0 ) . ceil ( ) as usize ;
for _ in 0 .. insertions {
2022-01-04 01:18:21 +03:00
let supplement = supplements [ pick ( 0 .. supplements . len ( ) ) ] ;
let start = pick ( 0 .. src . len ( ) ) ;
let end = pick ( start .. src . len ( ) ) ;
2022-01-02 02:46:19 +03:00
if ! src . is_char_boundary ( start ) | | ! src . is_char_boundary ( end ) {
continue ;
}
2022-01-04 01:18:21 +03:00
ok & = apply ( start .. end , supplement ) ;
2022-01-02 02:46:19 +03:00
}
2022-01-04 01:18:21 +03:00
let red = SourceFile ::detached ( src ) . red ( ) ;
let leafs = red . as_ref ( ) . leafs ( ) ;
let leaf_start = leafs [ pick ( 0 .. leafs . len ( ) ) ] . span ( ) . start ;
let supplement = supplements [ pick ( 0 .. supplements . len ( ) ) ] ;
2022-01-02 02:46:19 +03:00
ok & = apply ( leaf_start .. leaf_start , supplement ) ;
ok
}
2022-01-24 18:48:24 +03:00
#[ cfg(feature = " layout-cache " ) ]
fn test_incremental (
ctx : & mut Context ,
i : usize ,
tree : & RootNode ,
2022-01-31 18:06:44 +03:00
frames : & [ Arc < Frame > ] ,
2022-01-24 18:48:24 +03:00
) -> bool {
let mut ok = true ;
2021-01-13 16:07:38 +03:00
2022-01-24 18:48:24 +03:00
let reference = ctx . layout_cache . clone ( ) ;
for level in 0 .. reference . levels ( ) {
ctx . layout_cache = reference . clone ( ) ;
ctx . layout_cache . retain ( | x | x = = level ) ;
if ctx . layout_cache . is_empty ( ) {
2020-12-11 00:44:35 +03:00
continue ;
2021-01-13 19:22:33 +03:00
}
2020-12-11 00:44:35 +03:00
2022-01-24 18:48:24 +03:00
ctx . layout_cache . turnaround ( ) ;
2021-07-09 11:50:25 +03:00
2022-01-24 18:48:24 +03:00
let cached = silenced ( | | tree . layout ( ctx ) ) ;
let total = reference . levels ( ) - 1 ;
let misses = ctx
. layout_cache
. entries ( )
. filter ( | e | e . level ( ) = = level & & ! e . hit ( ) & & e . age ( ) = = 2 )
. count ( ) ;
2021-01-13 16:07:38 +03:00
2022-01-24 18:48:24 +03:00
if misses > 0 {
println! (
" Subtest {i} relayout had {misses} cache misses on level {level} of {total} ❌ " ,
) ;
ok = false ;
}
2021-02-17 23:30:20 +03:00
2022-01-24 18:48:24 +03:00
if cached ! = frames {
println! (
" Subtest {i} relayout differs from clean pass on level {level} ❌ " ,
) ;
ok = false ;
}
2021-01-13 19:22:33 +03:00
}
2020-12-11 00:44:35 +03:00
2022-01-24 18:48:24 +03:00
ctx . layout_cache = reference ;
ctx . layout_cache . turnaround ( ) ;
2021-07-30 19:04:08 +03:00
2022-01-24 18:48:24 +03:00
ok
2020-12-11 00:44:35 +03:00
}
2020-08-02 22:17:42 +03:00
2022-01-24 18:48:24 +03:00
/// Draw all frames into one image with padding in between.
2022-01-31 18:06:44 +03:00
fn render ( ctx : & mut Context , frames : & [ Arc < Frame > ] ) -> sk ::Pixmap {
2022-01-24 18:48:24 +03:00
let pixel_per_pt = 2.0 ;
let pixmaps : Vec < _ > = frames
. iter ( )
. map ( | frame | {
let limit = Length ::cm ( 100.0 ) ;
if frame . size . x > limit | | frame . size . y > limit {
panic! ( " overlarge frame: {:?} " , frame . size ) ;
}
typst ::export ::render ( ctx , frame , pixel_per_pt )
} )
. collect ( ) ;
2020-08-30 23:18:55 +03:00
2022-01-24 18:48:24 +03:00
let pad = ( 5.0 * pixel_per_pt ) . round ( ) as u32 ;
let pxw = 2 * pad + pixmaps . iter ( ) . map ( sk ::Pixmap ::width ) . max ( ) . unwrap_or_default ( ) ;
let pxh = pad + pixmaps . iter ( ) . map ( | pixmap | pixmap . height ( ) + pad ) . sum ::< u32 > ( ) ;
2021-01-14 01:19:44 +03:00
2021-11-16 23:32:29 +03:00
let mut canvas = sk ::Pixmap ::new ( pxw , pxh ) . unwrap ( ) ;
2021-07-08 23:33:44 +03:00
canvas . fill ( sk ::Color ::BLACK ) ;
2020-08-02 22:17:42 +03:00
2022-01-24 18:48:24 +03:00
let [ x , mut y ] = [ pad ; 2 ] ;
for ( frame , mut pixmap ) in frames . iter ( ) . zip ( pixmaps ) {
let ts = sk ::Transform ::from_scale ( pixel_per_pt , pixel_per_pt ) ;
render_links ( & mut pixmap , ts , ctx , frame ) ;
canvas . draw_pixmap (
x as i32 ,
y as i32 ,
pixmap . as_ref ( ) ,
& sk ::PixmapPaint ::default ( ) ,
sk ::Transform ::identity ( ) ,
None ,
) ;
y + = pixmap . 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
}
2022-01-24 18:48:24 +03:00
/// Draw extra boxes for links so we can see whether they are there.
fn render_links (
2021-11-16 23:32:29 +03:00
canvas : & mut sk ::Pixmap ,
ts : sk ::Transform ,
ctx : & Context ,
frame : & Frame ,
) {
for ( pos , element ) in & frame . elements {
2022-01-24 18:48:24 +03:00
let ts = ts . pre_translate ( pos . x . to_pt ( ) as f32 , pos . y . to_pt ( ) as f32 ) ;
2021-11-16 23:32:29 +03:00
match * element {
2021-11-22 17:26:56 +03:00
Element ::Group ( ref group ) = > {
2022-01-24 18:48:24 +03:00
let ts = ts . pre_concat ( group . transform . into ( ) ) ;
render_links ( canvas , ts , ctx , & group . frame ) ;
2021-03-23 17:32:36 +03:00
}
2022-01-24 18:48:24 +03:00
Element ::Link ( _ , size ) = > {
let w = size . x . to_pt ( ) as f32 ;
let h = size . y . to_pt ( ) as f32 ;
let rect = sk ::Rect ::from_xywh ( 0.0 , 0.0 , w , h ) . unwrap ( ) ;
let mut paint = sk ::Paint ::default ( ) ;
paint . set_color_rgba8 ( 40 , 54 , 99 , 40 ) ;
canvas . fill_rect ( rect , & paint , ts , None ) ;
2021-04-05 23:32:09 +03:00
}
2022-01-24 18:48:24 +03:00
_ = > { }
2021-03-23 17:32:36 +03:00
}
2021-11-20 17:51:07 +03:00
}
}
2021-11-30 01:22:53 +03:00
/// Disable stdout and stderr during execution of `f`.
2022-01-01 14:12:50 +03:00
#[ cfg(feature = " layout-cache " ) ]
2021-11-30 01:22:53 +03:00
fn silenced < F , T > ( f : F ) -> T
where
F : FnOnce ( ) -> T ,
{
let path = if cfg! ( windows ) { " NUL " } else { " /dev/null " } ;
let null = File ::create ( path ) . unwrap ( ) ;
let stderr = FileDescriptor ::redirect_stdio ( & null , Stderr ) . unwrap ( ) ;
let stdout = FileDescriptor ::redirect_stdio ( & null , Stdout ) . unwrap ( ) ;
let result = f ( ) ;
FileDescriptor ::redirect_stdio ( & stderr , Stderr ) . unwrap ( ) ;
FileDescriptor ::redirect_stdio ( & stdout , Stdout ) . unwrap ( ) ;
result
}
2022-01-02 02:46:19 +03:00
/// This is an Linear-feedback shift register using XOR as its shifting
/// function. It can be used as PRNG.
struct LinearShift ( u64 ) ;
impl LinearShift {
/// Initialize the shift register with a pre-set seed.
pub fn new ( ) -> Self {
Self ( 0xACE5 )
}
2022-01-04 01:18:21 +03:00
/// Return a pseudo-random number between `0.0` and `1.0`.
pub fn next ( & mut self ) -> f64 {
2022-01-02 02:46:19 +03:00
self . 0 ^ = self . 0 > > 3 ;
self . 0 ^ = self . 0 < < 14 ;
self . 0 ^ = self . 0 > > 28 ;
self . 0 ^ = self . 0 < < 36 ;
self . 0 ^ = self . 0 > > 52 ;
2022-01-04 01:18:21 +03:00
self . 0 as f64 / u64 ::MAX as f64
2022-01-02 02:46:19 +03:00
}
}