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 ;
2021-01-17 15:53:22 +03:00
use std ::rc ::Rc ;
2020-08-02 22:17:42 +03:00
2020-12-01 00:07:08 +03:00
use image ::{ GenericImageView , Rgba } ;
2021-07-08 23:33:44 +03:00
use tiny_skia as sk ;
2021-04-21 22:17:25 +03:00
use ttf_parser ::{ GlyphId , OutlineBuilder } ;
2021-12-06 16:58:57 +03:00
use usvg ::FitTo ;
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 } ;
2021-08-27 14:29:50 +03:00
use typst ::font ::Face ;
2022-01-01 14:12:50 +03:00
use typst ::frame ::{ Element , Frame , Geometry , Shape , Stroke , Text } ;
2021-12-07 18:36:39 +03:00
use typst ::geom ::{ self , Color , Length , Paint , PathElement , RgbaColor , Size , Transform } ;
2021-12-06 16:58:57 +03:00
use typst ::image ::{ Image , RasterImage , Svg } ;
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 {
2020-08-02 22:17:42 +03:00
println! ( " Running {} tests " , len ) ;
}
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 ( ) ;
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 {
println! ( " {} / {} tests passed. " , ok , len ) ;
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
2021-07-20 21:21:56 +03:00
let canvas = draw ( ctx , & frames , 2.0 ) ;
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 ,
2021-06-18 14:01:55 +03:00
) -> ( bool , bool , Vec < Rc < Frame > > ) {
2021-08-09 12:06:37 +03:00
let id = ctx . sources . provide ( src_path , src ) ;
let source = ctx . sources . get ( id ) ;
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-04 01:18:21 +03:00
let mut 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 {
2021-12-20 16:18:29 +03:00
println! ( " {:#?} " , 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 {
println! ( " Subtest {} does not match expected errors. ❌ " , i ) ;
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
2021-07-26 01:08:08 +03:00
#[ cfg(feature = " layout-cache " ) ]
fn test_incremental (
ctx : & mut Context ,
i : usize ,
2021-12-20 16:18:29 +03:00
tree : & RootNode ,
2021-07-26 01:08:08 +03:00
frames : & [ Rc < Frame > ] ,
) -> bool {
let mut ok = true ;
2021-06-27 13:28:40 +03:00
2021-07-26 01:08:08 +03:00
let reference = ctx . layouts . clone ( ) ;
for level in 0 .. reference . levels ( ) {
ctx . layouts = reference . clone ( ) ;
ctx . layouts . retain ( | x | x = = level ) ;
if ctx . layouts . is_empty ( ) {
continue ;
2021-06-27 13:28:40 +03:00
}
2021-07-20 21:21:56 +03:00
ctx . layouts . turnaround ( ) ;
2021-06-27 13:28:40 +03:00
2021-12-20 16:18:29 +03:00
let cached = silenced ( | | tree . layout ( ctx ) ) ;
2021-07-26 01:08:08 +03:00
let misses = ctx
. layouts
. entries ( )
. filter ( | e | e . level ( ) = = level & & ! e . hit ( ) & & e . age ( ) = = 2 )
. count ( ) ;
if misses > 0 {
println! (
2021-09-27 12:40:28 +03:00
" Subtest {} relayout had {} cache misses on level {} of {} ❌ " ,
i ,
misses ,
level ,
reference . levels ( ) - 1 ,
2021-07-26 01:08:08 +03:00
) ;
ok = false ;
}
if cached ! = frames {
2021-12-15 13:11:57 +03:00
println! (
" Subtest {} relayout differs from clean pass on level {} ❌ " ,
i , level
) ;
2021-07-26 01:08:08 +03:00
ok = false ;
}
2021-06-27 19:06:39 +03:00
}
2021-07-26 01:08:08 +03:00
ctx . layouts = reference ;
ctx . layouts . turnaround ( ) ;
ok
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 = [
" [ " ,
" ) " ,
" #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! (
" Subtest {} tree length {} does not match string length {} ❌ " ,
i ,
incr_source . root ( ) . len ( ) ,
src . len ( ) ,
) ;
return false ;
}
2022-01-02 02:46:19 +03:00
incr_source . edit ( replace . clone ( ) , with ) ;
let edited_src = incr_source . src ( ) ;
let ref_source = SourceFile ::detached ( edited_src ) ;
let incr_root = incr_source . root ( ) ;
let ref_root = ref_source . root ( ) ;
if incr_root ! = ref_root {
println! (
" Subtest {} reparse differs from clean parse when inserting '{}' at {}-{} ❌ " ,
i , with , replace . start , replace . end ,
) ;
println! (
" \n Expected reference tree: \n {:#?} \n \n Found incremental tree: \n {:#?} " ,
ref_root , incr_root
) ;
2022-01-02 16:46:08 +03:00
println! ( " Full source ( {} ): \n \" {:?} \" " , edited_src . len ( ) , edited_src ) ;
2022-01-02 02:46:19 +03:00
false
} else {
true
}
} ;
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 ( ) ;
2022-01-02 02:46:19 +03:00
2022-01-04 01:18:21 +03:00
let leafs = red . as_ref ( ) . leafs ( ) ;
2022-01-02 02:46:19 +03:00
2022-01-04 01:18:21 +03:00
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
}
2021-07-31 23:59:14 +03:00
fn parse_metadata ( source : & SourceFile ) -> ( Option < bool > , Vec < Error > ) {
2021-02-01 00:43:11 +03:00
let mut compare_ref = None ;
2021-07-30 19:04:08 +03:00
let mut errors = vec! [ ] ;
2020-12-11 00:44:35 +03:00
2021-07-31 23:59:14 +03:00
let lines : Vec < _ > = source . src ( ) . lines ( ) . map ( str ::trim ) . collect ( ) ;
2021-07-09 11:50:25 +03:00
for ( i , line ) in lines . iter ( ) . enumerate ( ) {
2021-02-01 00:43:11 +03:00
if line . starts_with ( " // Ref: false " ) {
compare_ref = Some ( false ) ;
}
if line . starts_with ( " // Ref: true " ) {
compare_ref = Some ( true ) ;
}
2021-01-13 16:07:38 +03:00
2021-07-30 19:04:08 +03:00
let rest = if let Some ( rest ) = line . strip_prefix ( " // Error: " ) {
rest
2020-12-11 00:44:35 +03:00
} else {
continue ;
} ;
2021-07-31 23:59:14 +03:00
fn num ( s : & mut Scanner ) -> usize {
2021-01-13 19:22:33 +03:00
s . eat_while ( | c | c . is_numeric ( ) ) . parse ( ) . unwrap ( )
}
2020-12-11 00:44:35 +03:00
2021-07-09 11:50:25 +03:00
let comments =
lines [ i .. ] . iter ( ) . take_while ( | line | line . starts_with ( " // " ) ) . count ( ) ;
2021-11-02 14:13:45 +03:00
let pos = | s : & mut Scanner | -> usize {
2021-07-31 23:59:14 +03:00
let first = num ( s ) - 1 ;
2021-01-30 18:46:16 +03:00
let ( delta , column ) =
2021-07-31 23:59:14 +03:00
if s . eat_if ( ':' ) { ( first , num ( s ) - 1 ) } else { ( 0 , first ) } ;
let line = ( i + comments ) + delta ;
2021-11-02 14:13:45 +03:00
source . line_column_to_byte ( line , column ) . unwrap ( )
2021-01-13 19:22:33 +03:00
} ;
2021-01-13 16:07:38 +03:00
2021-01-13 19:22:33 +03:00
let mut s = Scanner ::new ( rest ) ;
2021-02-17 23:30:20 +03:00
let start = pos ( & mut s ) ;
let end = if s . eat_if ( '-' ) { pos ( & mut s ) } else { start } ;
2021-08-13 13:21:14 +03:00
let span = Span ::new ( source . id ( ) , start , end ) ;
2021-02-17 23:30:20 +03:00
2021-08-13 13:21:14 +03:00
errors . push ( Error ::new ( span , s . rest ( ) . trim ( ) ) ) ;
2021-01-13 19:22:33 +03:00
}
2020-12-11 00:44:35 +03:00
2021-07-30 19:04:08 +03:00
( compare_ref , errors )
}
2021-07-31 23:59:14 +03:00
fn print_error ( source : & SourceFile , line : usize , error : & Error ) {
2021-11-02 14:13:45 +03:00
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 ( ) ;
2021-07-31 23:59:14 +03:00
println! (
" Error: {}:{}-{}:{}: {} " ,
start_line , start_col , end_line , end_col , error . message
) ;
2020-12-11 00:44:35 +03:00
}
2020-08-02 22:17:42 +03:00
2021-08-27 14:29:50 +03:00
fn draw ( ctx : & Context , frames : & [ Rc < Frame > ] , dpp : f32 ) -> sk ::Pixmap {
2020-11-25 20:46:47 +03:00
let pad = Length ::pt ( 5.0 ) ;
2021-11-26 18:32:06 +03:00
let width = 2.0 * pad + frames . iter ( ) . map ( | l | l . size . x ) . max ( ) . unwrap_or_default ( ) ;
let height = pad + frames . iter ( ) . map ( | l | l . size . y + pad ) . sum ::< Length > ( ) ;
2020-08-30 23:18:55 +03:00
2021-11-20 17:51:07 +03:00
let pxw = ( dpp * width . to_f32 ( ) ) as u32 ;
let pxh = ( dpp * height . to_f32 ( ) ) as u32 ;
2021-11-16 23:32:29 +03:00
if pxw > 4000 | | pxh > 4000 {
2021-03-12 15:57:47 +03:00
panic! (
2021-08-31 17:25:12 +03:00
" overlarge image: {} by {} ({:?} x {:?}) " ,
2021-11-16 23:32:29 +03:00
pxw , pxh , width , height ,
2021-03-12 15:57:47 +03:00
) ;
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
2021-11-16 23:32:29 +03:00
let mut mask = sk ::ClipMask ::new ( ) ;
let rect = sk ::Rect ::from_xywh ( 0.0 , 0.0 , pxw as f32 , pxh as f32 ) . unwrap ( ) ;
let path = sk ::PathBuilder ::from_rect ( rect ) ;
mask . set_path ( pxw , pxh , & path , sk ::FillRule ::default ( ) , false ) ;
2021-11-20 17:51:07 +03:00
let mut ts =
sk ::Transform ::from_scale ( dpp , dpp ) . pre_translate ( pad . to_f32 ( ) , pad . to_f32 ( ) ) ;
2021-11-16 23:32:29 +03:00
2021-01-03 02:12:09 +03:00
for frame in frames {
2021-11-16 23:32:29 +03:00
let mut background = sk ::Paint ::default ( ) ;
background . set_color ( sk ::Color ::WHITE ) ;
2020-08-02 22:17:42 +03:00
2021-11-26 18:32:06 +03:00
let w = frame . size . x . to_f32 ( ) ;
let h = frame . size . y . to_f32 ( ) ;
2021-11-16 23:32:29 +03:00
let rect = sk ::Rect ::from_xywh ( 0.0 , 0.0 , w , h ) . unwrap ( ) ;
canvas . fill_rect ( rect , & background , ts , None ) ;
2020-08-02 22:17:42 +03:00
2022-01-01 14:12:50 +03:00
draw_frame ( & mut canvas , ts , & mask , ctx , frame , true ) ;
2021-11-26 18:32:06 +03:00
ts = ts . pre_translate ( 0.0 , ( frame . size . y + pad ) . to_f32 ( ) ) ;
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
}
2021-11-16 23:32:29 +03:00
fn draw_frame (
canvas : & mut sk ::Pixmap ,
ts : sk ::Transform ,
mask : & sk ::ClipMask ,
ctx : & Context ,
frame : & Frame ,
2022-01-01 14:12:50 +03:00
clip : bool ,
2021-11-16 23:32:29 +03:00
) {
2022-01-01 14:12:50 +03:00
let mut storage ;
let mut mask = mask ;
if clip {
let w = frame . size . x . to_f32 ( ) ;
let h = frame . size . y . to_f32 ( ) ;
let rect = sk ::Rect ::from_xywh ( 0.0 , 0.0 , w , h ) . unwrap ( ) ;
let path = sk ::PathBuilder ::from_rect ( rect ) . transform ( ts ) . unwrap ( ) ;
let rule = sk ::FillRule ::default ( ) ;
storage = mask . clone ( ) ;
if storage . intersect_path ( & path , rule , false ) . is_none ( ) {
// Fails if clipping rect is empty. In that case we just clip
// everything by returning.
return ;
}
mask = & storage ;
}
2021-11-16 23:32:29 +03:00
for ( pos , element ) in & frame . elements {
2021-11-20 17:51:07 +03:00
let x = pos . x . to_f32 ( ) ;
let y = pos . y . to_f32 ( ) ;
2021-11-16 23:32:29 +03:00
let ts = ts . pre_translate ( x , y ) ;
match * element {
2021-11-22 17:26:56 +03:00
Element ::Group ( ref group ) = > {
2022-01-01 14:12:50 +03:00
let ts = ts . pre_concat ( convert_typst_transform ( group . transform ) ) ;
draw_frame ( canvas , ts , & mask , ctx , & group . frame , group . clips ) ;
2021-11-22 17:26:56 +03:00
}
2021-11-16 23:32:29 +03:00
Element ::Text ( ref text ) = > {
draw_text ( canvas , ts , mask , ctx . fonts . get ( text . face_id ) , text ) ;
}
2021-11-20 17:51:07 +03:00
Element ::Shape ( ref shape ) = > {
draw_shape ( canvas , ts , mask , shape ) ;
2021-11-16 23:32:29 +03:00
}
Element ::Image ( id , size ) = > {
draw_image ( canvas , ts , mask , ctx . images . get ( id ) , size ) ;
}
Element ::Link ( _ , s ) = > {
2021-11-20 17:51:07 +03:00
let fill = RgbaColor ::new ( 40 , 54 , 99 , 40 ) . into ( ) ;
let shape = Shape ::filled ( Geometry ::Rect ( s ) , fill ) ;
draw_shape ( canvas , ts , mask , & shape ) ;
2021-11-16 23:32:29 +03:00
}
}
}
}
fn draw_text (
canvas : & mut sk ::Pixmap ,
ts : sk ::Transform ,
mask : & sk ::ClipMask ,
face : & Face ,
text : & Text ,
) {
2021-08-27 14:29:50 +03:00
let ttf = face . ttf ( ) ;
2021-11-20 17:51:07 +03:00
let size = text . size . to_f32 ( ) ;
let units_per_em = face . units_per_em as f32 ;
let pixels_per_em = text . size . to_f32 ( ) * ts . sy ;
2021-08-27 14:29:50 +03:00
let scale = size / units_per_em ;
2020-08-02 22:17:42 +03:00
2021-08-27 14:29:50 +03:00
let mut x = 0.0 ;
2021-05-14 12:14:28 +03:00
for glyph in & text . glyphs {
2021-08-27 14:29:50 +03:00
let glyph_id = GlyphId ( glyph . id ) ;
2021-12-15 13:11:57 +03:00
let offset = x + glyph . x_offset . resolve ( text . size ) . to_f32 ( ) ;
2021-08-27 14:29:50 +03:00
let ts = ts . pre_translate ( offset , 0.0 ) ;
2020-11-25 20:46:47 +03:00
2021-03-24 19:12:34 +03:00
if let Some ( tree ) = ttf
2021-08-27 14:29:50 +03:00
. glyph_svg_image ( glyph_id )
2021-03-23 17:32:36 +03:00
. and_then ( | data | std ::str ::from_utf8 ( data ) . ok ( ) )
. map ( | svg | {
let viewbox = format! ( " viewBox= \" 0 0 {0} {0} \" xmlns " , units_per_em ) ;
svg . replace ( " xmlns " , & viewbox )
} )
2021-12-06 16:58:57 +03:00
. and_then ( | s | {
usvg ::Tree ::from_str ( & s , & usvg ::Options ::default ( ) . to_ref ( ) ) . ok ( )
} )
2021-03-23 17:32:36 +03:00
{
for child in tree . root ( ) . children ( ) {
if let usvg ::NodeKind ::Path ( node ) = & * child . borrow ( ) {
2021-08-27 14:29:50 +03:00
// SVG is already Y-down, no flipping required.
2021-03-24 19:12:34 +03:00
let ts = convert_usvg_transform ( node . transform )
2021-08-27 14:29:50 +03:00
. post_scale ( scale , scale )
2021-03-23 17:32:36 +03:00
. post_concat ( ts ) ;
2021-08-27 14:29:50 +03:00
2021-03-23 17:32:36 +03:00
if let Some ( fill ) = & node . fill {
2021-08-27 14:29:50 +03:00
let path = convert_usvg_path ( & node . data ) ;
2021-03-23 17:32:36 +03:00
let ( paint , fill_rule ) = convert_usvg_fill ( fill ) ;
2021-11-16 23:32:29 +03:00
canvas . fill_path ( & path , & paint , fill_rule , ts , Some ( mask ) ) ;
2021-03-23 17:32:36 +03:00
}
}
}
2021-08-27 14:29:50 +03:00
} else if let Some ( raster ) =
ttf . glyph_raster_image ( glyph_id , pixels_per_em as u16 )
{
// TODO: Vertical alignment isn't quite right for Apple Color Emoji,
// and maybe also for Noto Color Emoji. And: Is the size calculation
// correct?
2021-12-06 16:58:57 +03:00
let img = RasterImage ::parse ( & raster . data ) . unwrap ( ) ;
2021-08-27 14:29:50 +03:00
let h = text . size ;
let w = ( img . width ( ) as f64 / img . height ( ) as f64 ) * h ;
let dx = ( raster . x as f32 ) / ( img . width ( ) as f32 ) * size ;
let dy = ( raster . y as f32 ) / ( img . height ( ) as f32 ) * size ;
let ts = ts . pre_translate ( dx , - size - dy ) ;
2021-12-06 16:58:57 +03:00
draw_image ( canvas , ts , mask , & Image ::Raster ( img ) , Size ::new ( w , h ) ) ;
2021-04-05 23:32:09 +03:00
} else {
// Otherwise, draw normal outline.
2021-07-08 23:33:44 +03:00
let mut builder = WrappedPathBuilder ( sk ::PathBuilder ::new ( ) ) ;
2021-08-27 14:29:50 +03:00
if ttf . outline_glyph ( glyph_id , & mut builder ) . is_some ( ) {
2021-11-20 17:51:07 +03:00
// Flip vertically because font design coordinate system is Y-up.
2021-08-27 14:29:50 +03:00
let ts = ts . pre_scale ( scale , - scale ) ;
2021-04-05 23:32:09 +03:00
let path = builder . 0. finish ( ) . unwrap ( ) ;
2021-11-20 17:51:07 +03:00
let paint = convert_typst_paint ( text . fill ) ;
2021-11-16 23:32:29 +03:00
canvas . fill_path ( & path , & paint , sk ::FillRule ::default ( ) , ts , Some ( mask ) ) ;
2021-04-05 23:32:09 +03:00
}
2021-03-23 17:32:36 +03:00
}
2020-08-02 22:17:42 +03:00
2021-12-15 13:11:57 +03:00
x + = glyph . x_advance . resolve ( text . size ) . to_f32 ( ) ;
2020-08-02 22:17:42 +03:00
}
}
2021-11-20 17:51:07 +03:00
fn draw_shape (
2021-07-08 23:33:44 +03:00
canvas : & mut sk ::Pixmap ,
ts : sk ::Transform ,
2021-11-16 23:32:29 +03:00
mask : & sk ::ClipMask ,
2021-11-20 17:51:07 +03:00
shape : & Shape ,
2021-07-08 23:33:44 +03:00
) {
2021-11-20 17:51:07 +03:00
let path = match shape . geometry {
Geometry ::Rect ( size ) = > {
2021-11-26 18:32:06 +03:00
let w = size . x . to_f32 ( ) ;
let h = size . y . to_f32 ( ) ;
2021-07-08 23:33:44 +03:00
let rect = sk ::Rect ::from_xywh ( 0.0 , 0.0 , w , h ) . unwrap ( ) ;
2021-11-20 17:51:07 +03:00
sk ::PathBuilder ::from_rect ( rect )
2021-03-20 22:19:30 +03:00
}
2021-07-08 23:33:44 +03:00
Geometry ::Ellipse ( size ) = > {
2021-11-20 17:51:07 +03:00
let approx = geom ::Path ::ellipse ( size ) ;
convert_typst_path ( & approx )
2021-06-11 00:08:52 +03:00
}
2021-11-20 17:51:07 +03:00
Geometry ::Line ( target ) = > {
let mut builder = sk ::PathBuilder ::new ( ) ;
builder . line_to ( target . x . to_f32 ( ) , target . y . to_f32 ( ) ) ;
builder . finish ( ) . unwrap ( )
2021-02-06 14:54:44 +03:00
}
2021-11-20 17:51:07 +03:00
Geometry ::Path ( ref path ) = > convert_typst_path ( path ) ,
2021-02-06 14:30:44 +03:00
} ;
2021-11-20 17:51:07 +03:00
if let Some ( fill ) = shape . fill {
let mut paint = convert_typst_paint ( fill ) ;
if matches! ( shape . geometry , Geometry ::Rect ( _ ) ) {
paint . anti_alias = false ;
}
let rule = sk ::FillRule ::default ( ) ;
canvas . fill_path ( & path , & paint , rule , ts , Some ( mask ) ) ;
}
if let Some ( Stroke { paint , thickness } ) = shape . stroke {
let paint = convert_typst_paint ( paint ) ;
let mut stroke = sk ::Stroke ::default ( ) ;
stroke . width = thickness . to_f32 ( ) ;
canvas . stroke_path ( & path , & paint , & stroke , ts , Some ( mask ) ) ;
}
2021-02-04 23:30:18 +03:00
}
2021-11-16 23:32:29 +03:00
fn draw_image (
canvas : & mut sk ::Pixmap ,
ts : sk ::Transform ,
mask : & sk ::ClipMask ,
img : & Image ,
size : Size ,
) {
2021-11-26 18:32:06 +03:00
let view_width = size . x . to_f32 ( ) ;
let view_height = size . y . to_f32 ( ) ;
2021-12-06 16:58:57 +03:00
let pixmap = match img {
Image ::Raster ( img ) = > {
let w = img . buf . width ( ) ;
let h = img . buf . height ( ) ;
let mut pixmap = sk ::Pixmap ::new ( w , h ) . unwrap ( ) ;
for ( ( _ , _ , src ) , dest ) in img . buf . pixels ( ) . zip ( pixmap . pixels_mut ( ) ) {
let Rgba ( [ r , g , b , a ] ) = src ;
* dest = sk ::ColorU8 ::from_rgba ( r , g , b , a ) . premultiply ( ) ;
}
pixmap
}
Image ::Svg ( Svg ( tree ) ) = > {
let size = tree . svg_node ( ) . size ;
let aspect = ( size . width ( ) / size . height ( ) ) as f32 ;
let scale = ts . sx . max ( ts . sy ) ;
let w = ( scale * view_width . max ( aspect * view_height ) ) . ceil ( ) as u32 ;
let h = ( ( w as f32 ) / aspect ) . ceil ( ) as u32 ;
let mut pixmap = sk ::Pixmap ::new ( w , h ) . unwrap ( ) ;
resvg ::render ( & tree , FitTo ::Size ( w , h ) , pixmap . as_mut ( ) ) ;
pixmap
}
} ;
let scale_x = view_width / pixmap . width ( ) as f32 ;
let scale_y = view_height / pixmap . height ( ) as f32 ;
2020-11-25 20:46:47 +03:00
2021-07-08 23:33:44 +03:00
let mut paint = sk ::Paint ::default ( ) ;
paint . shader = sk ::Pattern ::new (
2021-03-23 15:17:00 +03:00
pixmap . as_ref ( ) ,
2021-07-08 23:33:44 +03:00
sk ::SpreadMode ::Pad ,
sk ::FilterQuality ::Bilinear ,
2020-11-25 20:46:47 +03:00
1.0 ,
2021-08-27 14:29:50 +03:00
sk ::Transform ::from_scale ( scale_x , scale_y ) ,
2020-11-25 20:46:47 +03:00
) ;
2021-07-08 23:33:44 +03:00
let rect = sk ::Rect ::from_xywh ( 0.0 , 0.0 , view_width , view_height ) . unwrap ( ) ;
2021-11-16 23:32:29 +03:00
canvas . fill_rect ( rect , & paint , ts , Some ( mask ) ) ;
2020-11-20 18:36:22 +03:00
}
2021-11-24 00:04:08 +03:00
fn convert_typst_transform ( transform : Transform ) -> sk ::Transform {
let Transform { sx , ky , kx , sy , tx , ty } = transform ;
sk ::Transform ::from_row (
sx . get ( ) as _ ,
ky . get ( ) as _ ,
kx . get ( ) as _ ,
sy . get ( ) as _ ,
tx . to_f32 ( ) ,
ty . to_f32 ( ) ,
)
}
2021-07-08 23:33:44 +03:00
fn convert_typst_paint ( paint : Paint ) -> sk ::Paint < 'static > {
2021-11-20 17:51:07 +03:00
let Paint ::Solid ( Color ::Rgba ( c ) ) = paint ;
2021-07-08 23:33:44 +03:00
let mut paint = sk ::Paint ::default ( ) ;
paint . set_color_rgba8 ( c . r , c . g , c . b , c . a ) ;
2021-11-20 17:51:07 +03:00
paint . anti_alias = true ;
2021-03-20 22:19:30 +03:00
paint
}
2021-07-08 23:33:44 +03:00
fn convert_typst_path ( path : & geom ::Path ) -> sk ::Path {
let mut builder = sk ::PathBuilder ::new ( ) ;
2021-03-20 22:19:30 +03:00
for elem in & path . 0 {
match elem {
2021-07-08 23:33:44 +03:00
PathElement ::MoveTo ( p ) = > {
2021-11-20 17:51:07 +03:00
builder . move_to ( p . x . to_f32 ( ) , p . y . to_f32 ( ) ) ;
2021-05-05 23:18:33 +03:00
}
2021-07-08 23:33:44 +03:00
PathElement ::LineTo ( p ) = > {
2021-11-20 17:51:07 +03:00
builder . line_to ( p . x . to_f32 ( ) , p . y . to_f32 ( ) ) ;
2021-05-05 23:18:33 +03:00
}
2021-07-08 23:33:44 +03:00
PathElement ::CubicTo ( p1 , p2 , p3 ) = > {
2021-11-20 17:51:07 +03:00
builder . cubic_to (
p1 . x . to_f32 ( ) ,
p1 . y . to_f32 ( ) ,
p2 . x . to_f32 ( ) ,
p2 . y . to_f32 ( ) ,
p3 . x . to_f32 ( ) ,
p3 . y . to_f32 ( ) ,
) ;
2021-05-05 23:18:33 +03:00
}
2021-07-08 23:33:44 +03:00
PathElement ::ClosePath = > {
2021-05-05 23:18:33 +03:00
builder . close ( ) ;
2021-03-23 17:32:36 +03:00
}
2021-03-20 22:19:30 +03:00
} ;
}
builder . finish ( ) . unwrap ( )
}
2021-07-08 23:33:44 +03:00
fn convert_usvg_transform ( transform : usvg ::Transform ) -> sk ::Transform {
2021-05-05 23:18:33 +03:00
let usvg ::Transform { a , b , c , d , e , f } = transform ;
2021-11-20 17:51:07 +03:00
sk ::Transform ::from_row ( a as _ , b as _ , c as _ , d as _ , e as _ , f as _ )
2021-05-05 23:18:33 +03:00
}
2021-07-08 23:33:44 +03:00
fn convert_usvg_fill ( fill : & usvg ::Fill ) -> ( sk ::Paint < 'static > , sk ::FillRule ) {
let mut paint = sk ::Paint ::default ( ) ;
2021-03-23 17:32:36 +03:00
paint . anti_alias = true ;
2021-12-06 16:58:57 +03:00
if let usvg ::Paint ::Color ( usvg ::Color { red , green , blue , alpha : _ } ) = fill . paint {
2021-11-20 17:51:07 +03:00
paint . set_color_rgba8 ( red , green , blue , fill . opacity . to_u8 ( ) )
2021-03-23 17:32:36 +03:00
}
let rule = match fill . rule {
2021-07-08 23:33:44 +03:00
usvg ::FillRule ::NonZero = > sk ::FillRule ::Winding ,
usvg ::FillRule ::EvenOdd = > sk ::FillRule ::EvenOdd ,
2021-03-23 17:32:36 +03:00
} ;
( paint , rule )
}
2021-07-08 23:33:44 +03:00
fn convert_usvg_path ( path : & usvg ::PathData ) -> sk ::Path {
let mut builder = sk ::PathBuilder ::new ( ) ;
2021-03-23 17:32:36 +03:00
for seg in path . iter ( ) {
match * seg {
2021-05-05 23:18:33 +03:00
usvg ::PathSegment ::MoveTo { x , y } = > {
2021-11-20 17:51:07 +03:00
builder . move_to ( x as _ , y as _ ) ;
2021-05-05 23:18:33 +03:00
}
2021-03-23 17:32:36 +03:00
usvg ::PathSegment ::LineTo { x , y } = > {
2021-11-20 17:51:07 +03:00
builder . line_to ( x as _ , y as _ ) ;
2021-03-23 17:32:36 +03:00
}
usvg ::PathSegment ::CurveTo { x1 , y1 , x2 , y2 , x , y } = > {
2021-11-20 17:51:07 +03:00
builder . cubic_to ( x1 as _ , y1 as _ , x2 as _ , y2 as _ , x as _ , y as _ ) ;
2021-05-05 23:18:33 +03:00
}
usvg ::PathSegment ::ClosePath = > {
builder . close ( ) ;
2021-03-23 17:32:36 +03:00
}
}
}
builder . finish ( ) . unwrap ( )
}
2021-07-08 23:33:44 +03:00
struct WrappedPathBuilder ( sk ::PathBuilder ) ;
2020-08-02 22:17:42 +03:00
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 ( ) ;
}
}
2021-11-20 17:51:07 +03:00
/// Additional methods for [`Length`].
trait LengthExt {
/// Convert an em length to a number of points.
fn to_f32 ( self ) -> f32 ;
}
impl LengthExt for Length {
fn to_f32 ( self ) -> f32 {
self . to_pt ( ) as f32
}
}
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
}
}