2020-08-02 21:17:42 +02:00
use std ::env ;
use std ::ffi ::OsStr ;
2021-11-29 23:22:53 +01:00
use std ::fs ::{ self , File } ;
2020-08-17 16:18:55 +02:00
use std ::path ::Path ;
2021-01-17 13:53:22 +01:00
use std ::rc ::Rc ;
2020-08-02 21:17:42 +02:00
2021-11-29 23:22:53 +01:00
use filedescriptor ::{ FileDescriptor , StdioDescriptor ::* } ;
2020-11-30 22:07:08 +01:00
use image ::{ GenericImageView , Rgba } ;
2021-07-08 22:33:44 +02:00
use tiny_skia as sk ;
2021-04-21 21:17:25 +02:00
use ttf_parser ::{ GlyphId , OutlineBuilder } ;
2021-12-06 14:58:57 +01:00
use usvg ::FitTo ;
2021-01-14 16:47:29 +01:00
use walkdir ::WalkDir ;
2020-08-02 21:17:42 +02:00
2021-08-17 22:04:18 +02:00
use typst ::diag ::Error ;
2021-11-22 14:30:43 +01:00
use typst ::eval ::{ Smart , Value } ;
2021-08-27 13:29:50 +02:00
use typst ::font ::Face ;
2021-11-22 15:26:56 +01:00
use typst ::frame ::{ Element , Frame , Geometry , Group , Shape , Stroke , Text } ;
2021-11-23 22:04:08 +01:00
use typst ::geom ::{
self , Color , Length , Paint , PathElement , RgbaColor , Sides , Size , Transform ,
} ;
2021-12-06 14:58:57 +01:00
use typst ::image ::{ Image , RasterImage , Svg } ;
2021-10-31 15:52:16 +01:00
use typst ::layout ::layout ;
2021-08-19 15:07:11 +02:00
#[ cfg(feature = " layout-cache " ) ]
2021-11-16 10:41:30 +01:00
use typst ::library ::DocumentNode ;
2021-08-09 11:06:37 +02:00
use typst ::loading ::FsLoader ;
2021-08-17 22:04:18 +02:00
use typst ::parse ::Scanner ;
use typst ::source ::SourceFile ;
2021-10-10 20:54:13 +02:00
use typst ::style ::Style ;
2021-11-02 12:13:45 +01:00
use typst ::syntax ::Span ;
2021-07-20 20:21:56 +02:00
use typst ::Context ;
2020-08-02 21:17:42 +02:00
2021-02-20 17:53:40 +01:00
const TYP_DIR : & str = " ./typ " ;
const REF_DIR : & str = " ./ref " ;
const PNG_DIR : & str = " ./png " ;
const PDF_DIR : & str = " ./pdf " ;
2020-12-10 22:44:35 +01:00
const FONT_DIR : & str = " ../fonts " ;
2020-08-02 21:17:42 +02:00
fn main ( ) {
2020-11-20 16:36:22 +01:00
env ::set_current_dir ( env ::current_dir ( ) . unwrap ( ) . join ( " tests " ) ) . unwrap ( ) ;
2021-03-13 14:18:31 +01:00
let args = Args ::new ( env ::args ( ) . skip ( 1 ) ) ;
2020-08-02 21:17:42 +02:00
let mut filtered = Vec ::new ( ) ;
2021-01-16 15:28:03 +01:00
for entry in WalkDir ::new ( " . " ) . into_iter ( ) {
2021-01-14 16:47:29 +01:00
let entry = entry . unwrap ( ) ;
2021-01-16 15:28:03 +01:00
if entry . depth ( ) < = 1 {
continue ;
}
2021-01-14 16:47:29 +01:00
let src_path = entry . into_path ( ) ;
2020-10-13 12:34:11 +02:00
if src_path . extension ( ) ! = Some ( OsStr ::new ( " typ " ) ) {
2020-08-02 21:17:42 +02:00
continue ;
}
2021-03-13 14:18:31 +01:00
if args . matches ( & src_path . to_string_lossy ( ) ) {
2021-01-14 16:47:29 +01:00
filtered . push ( src_path ) ;
2020-08-02 21:17:42 +02:00
}
}
let len = filtered . len ( ) ;
2021-01-13 21:33:22 +01:00
if len = = 1 {
2020-08-02 21:17:42 +02:00
println! ( " Running test ... " ) ;
2021-01-13 21:33:22 +01:00
} else if len > 1 {
2020-08-02 21:17:42 +02:00
println! ( " Running {} tests " , len ) ;
}
2021-07-20 20:21:56 +02:00
// We want to have "unbounded" pages, so we allow them to be infinitely
// large and fit them to match their content.
2021-10-10 20:54:13 +02:00
let mut style = Style ::default ( ) ;
style . page_mut ( ) . size = Size ::new ( Length ::pt ( 120.0 ) , Length ::inf ( ) ) ;
2021-11-22 14:30:43 +01:00
style . page_mut ( ) . margins = Sides ::splat ( Smart ::Custom ( Length ::pt ( 10.0 ) . into ( ) ) ) ;
2021-10-10 20:54:13 +02:00
style . text_mut ( ) . size = Length ::pt ( 10.0 ) ;
2021-07-20 20:21:56 +02:00
2021-07-30 18:04:08 +02:00
// Hook up an assert function into the global scope.
2021-07-21 11:25:49 +02:00
let mut std = typst ::library ::new ( ) ;
2021-07-30 18:04:08 +02: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 13:39:33 +02:00
return Err ( Error ::boxed (
2021-07-30 18:04:08 +02:00
args . span ,
2021-08-12 13:39:33 +02:00
format! ( " Assertion failed: {:?} != {:?} " , lhs , rhs ) ,
) ) ;
2021-07-30 18:04:08 +02:00
}
Ok ( Value ::None )
} ) ;
2020-08-02 21:17:42 +02:00
2021-07-21 11:25:49 +02:00
// Create loader and context.
let loader = FsLoader ::new ( ) . with_path ( FONT_DIR ) . wrap ( ) ;
2021-10-10 20:54:13 +02:00
let mut ctx = Context ::builder ( ) . std ( std ) . style ( style ) . build ( loader ) ;
2020-08-02 21:17:42 +02:00
2021-07-21 11:25:49 +02:00
// Run all the tests.
2021-01-13 21:33:22 +01:00
let mut ok = true ;
2021-01-14 16:47:29 +01:00
for src_path in filtered {
2021-03-13 14:18:31 +01: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 " ) ) ;
ok & = test (
2021-07-20 20:21:56 +02:00
& mut ctx ,
2021-03-13 14:18:31 +01:00
& src_path ,
& png_path ,
& ref_path ,
pdf_path . as_deref ( ) ,
) ;
2021-01-13 14:07:38 +01:00
}
2020-10-13 12:34:11 +02:00
if ! ok {
std ::process ::exit ( 1 ) ;
}
}
2020-08-02 21:17:42 +02:00
2021-03-13 14:18:31 +01:00
struct Args {
2020-08-02 21:17:42 +02:00
filter : Vec < String > ,
2021-03-13 14:18:31 +01:00
pdf : bool ,
2020-08-02 21:17:42 +02:00
perfect : bool ,
}
2021-03-13 14:18:31 +01:00
impl Args {
2020-08-03 16:01:23 +02:00
fn new ( args : impl Iterator < Item = String > ) -> Self {
2020-08-02 21:17:42 +02:00
let mut filter = Vec ::new ( ) ;
let mut perfect = false ;
2021-03-13 14:18:31 +01:00
let mut pdf = false ;
2020-08-02 21:17:42 +02:00
for arg in args {
match arg . as_str ( ) {
2020-08-03 16:01:23 +02:00
" --nocapture " = > { }
2021-03-13 14:18:31 +01:00
" --pdf " = > pdf = true ,
2020-08-02 21:17:42 +02:00
" = " = > perfect = true ,
_ = > filter . push ( arg ) ,
}
}
2021-03-13 14:18:31 +01:00
Self { filter , pdf , perfect }
2020-08-02 21:17:42 +02:00
}
fn matches ( & self , name : & str ) -> bool {
if self . perfect {
self . filter . iter ( ) . any ( | p | name = = p )
} else {
2020-08-30 22:18:55 +02:00
self . filter . is_empty ( ) | | self . filter . iter ( ) . any ( | p | name . contains ( p ) )
2020-08-02 21:17:42 +02:00
}
}
}
2020-12-10 22:44:35 +01:00
fn test (
2021-07-20 20:21:56 +02:00
ctx : & mut Context ,
2020-12-10 22:44:35 +01:00
src_path : & Path ,
png_path : & Path ,
2021-02-20 17:53:40 +01:00
ref_path : & Path ,
2021-03-13 14:18:31 +01:00
pdf_path : Option < & Path > ,
2020-12-10 22:44:35 +01:00
) -> bool {
2021-01-14 16:47:29 +01:00
let name = src_path . strip_prefix ( TYP_DIR ) . unwrap_or ( src_path ) ;
println! ( " Testing {} " , name . display ( ) ) ;
2020-12-10 22:44:35 +01:00
let src = fs ::read_to_string ( src_path ) . unwrap ( ) ;
2021-01-13 14:07:38 +01:00
let mut ok = true ;
let mut frames = vec! [ ] ;
2021-07-31 22:59:14 +02:00
let mut line = 0 ;
2021-01-31 22:43:11 +01:00
let mut compare_ref = true ;
2021-03-12 13:57:47 +01:00
let mut compare_ever = false ;
2021-01-31 22:43:11 +01:00
2021-06-29 16:05:05 +02:00
let parts : Vec < _ > = src . split ( " \n --- " ) . collect ( ) ;
2021-07-31 22:59:14 +02:00
for ( i , & part ) in parts . iter ( ) . enumerate ( ) {
2021-01-31 22:43:11 +01: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 {
2021-03-12 13:57:47 +01:00
let ( part_ok , compare_here , part_frames ) =
2021-08-09 11:06:37 +02:00
test_part ( ctx , src_path , part . into ( ) , i , compare_ref , line ) ;
2021-01-31 22:43:11 +01:00
ok & = part_ok ;
2021-03-12 13:57:47 +01:00
compare_ever | = compare_here ;
2021-01-31 22:43:11 +01:00
frames . extend ( part_frames ) ;
}
2021-07-31 22:59:14 +02:00
line + = part . lines ( ) . count ( ) + 1 ;
2021-01-13 14:07:38 +01:00
}
2021-03-12 13:57:47 +01:00
if compare_ever {
2021-03-13 14:18:31 +01:00
if let Some ( pdf_path ) = pdf_path {
2021-07-20 20:21:56 +02:00
let pdf_data = typst ::export ::pdf ( ctx , & frames ) ;
2021-03-13 14:18:31 +01:00
fs ::create_dir_all ( & pdf_path . parent ( ) . unwrap ( ) ) . unwrap ( ) ;
fs ::write ( pdf_path , pdf_data ) . unwrap ( ) ;
}
2021-01-13 14:07:38 +01:00
2021-07-20 20:21:56 +02:00
let canvas = draw ( ctx , & frames , 2.0 ) ;
2021-01-14 16:47:29 +01:00
fs ::create_dir_all ( & png_path . parent ( ) . unwrap ( ) ) . unwrap ( ) ;
2021-03-23 13:17:00 +01:00
canvas . save_png ( png_path ) . unwrap ( ) ;
2021-01-13 23:19:44 +01:00
2021-07-08 22:33:44 +02:00
if let Ok ( ref_pixmap ) = sk ::Pixmap ::load_png ( ref_path ) {
2021-03-23 13:17:00 +01:00
if canvas ! = ref_pixmap {
2021-02-20 17:53:40 +01:00
println! ( " Does not match reference image. ❌ " ) ;
2021-01-13 14:07:38 +01:00
ok = false ;
}
2021-07-30 18:04:08 +02:00
} else if ! frames . is_empty ( ) {
2021-02-20 17:53:40 +01:00
println! ( " Failed to open reference image. ❌ " ) ;
ok = false ;
2021-01-13 14:07:38 +01:00
}
}
if ok {
2021-01-14 16:47:29 +01:00
println! ( " \x1b [1ATesting {} ✔ " , name . display ( ) ) ;
2021-01-13 14:07:38 +01:00
}
ok
}
2021-01-31 22:43:11 +01:00
fn test_part (
2021-07-20 20:21:56 +02:00
ctx : & mut Context ,
2021-08-09 11:06:37 +02:00
src_path : & Path ,
src : String ,
2021-01-31 22:43:11 +01:00
i : usize ,
compare_ref : bool ,
2021-07-31 22:59:14 +02:00
line : usize ,
2021-06-18 13:01:55 +02:00
) -> ( bool , bool , Vec < Rc < Frame > > ) {
2021-08-09 11:06:37 +02:00
let id = ctx . sources . provide ( src_path , src ) ;
let source = ctx . sources . get ( id ) ;
2021-07-31 22:59:14 +02:00
let ( local_compare_ref , mut ref_errors ) = parse_metadata ( & source ) ;
2021-01-31 22:43:11 +01:00
let compare_ref = local_compare_ref . unwrap_or ( compare_ref ) ;
2020-12-10 22:44:35 +01:00
2021-07-30 18:04:08 +02:00
let mut ok = true ;
2021-08-17 22:04:18 +02:00
let ( frames , mut errors ) = match ctx . execute ( id ) {
2021-11-16 10:41:30 +01:00
Ok ( document ) = > {
let mut frames = layout ( ctx , & document ) ;
2021-06-18 13:00:36 +02:00
2021-07-30 18:04:08 +02:00
#[ cfg(feature = " layout-cache " ) ]
2021-11-16 10:41:30 +01:00
( ok & = test_incremental ( ctx , i , & document , & frames ) ) ;
2021-06-27 18:06:39 +02:00
2021-07-30 18:04:08 +02:00
if ! compare_ref {
frames . clear ( ) ;
}
2021-07-26 00:08:08 +02:00
2021-07-30 18:04:08 +02:00
( frames , vec! [ ] )
2021-01-26 20:49:45 +01:00
}
2021-07-30 18:04:08 +02:00
Err ( errors ) = > ( vec! [ ] , * errors ) ,
} ;
2021-01-17 13:53:22 +01:00
2021-07-30 18:04:08 +02:00
// TODO: Also handle errors from other files.
2021-08-13 12:21:14 +02:00
errors . retain ( | error | error . span . source = = id ) ;
2021-07-31 22:59:14 +02:00
for error in & mut errors {
error . trace . clear ( ) ;
}
2021-08-21 16:38:51 +02: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 11:25:49 +02:00
2021-07-30 18:04:08 +02:00
if errors ! = ref_errors {
println! ( " Subtest {} does not match expected errors. ❌ " , i ) ;
2020-12-10 22:44:35 +01:00
ok = false ;
2021-08-09 11:06:37 +02:00
let source = ctx . sources . get ( id ) ;
2021-07-30 18:04:08 +02:00
for error in errors . iter ( ) {
2021-08-13 12:21:14 +02:00
if error . span . source = = id & & ! ref_errors . contains ( error ) {
2021-01-14 17:41:13 +01:00
print! ( " Not annotated | " ) ;
2021-07-31 22:59:14 +02:00
print_error ( & source , line , error ) ;
2020-12-10 22:44:35 +01:00
}
}
2021-07-30 18:04:08 +02:00
for error in ref_errors . iter ( ) {
if ! errors . contains ( error ) {
2021-01-14 17:41:13 +01:00
print! ( " Not emitted | " ) ;
2021-07-31 22:59:14 +02:00
print_error ( & source , line , error ) ;
2020-12-10 22:44:35 +01:00
}
}
}
2021-07-26 00:08:08 +02:00
( ok , compare_ref , frames )
}
2021-06-27 12:28:40 +02:00
2021-07-26 00:08:08 +02:00
#[ cfg(feature = " layout-cache " ) ]
fn test_incremental (
ctx : & mut Context ,
i : usize ,
2021-11-16 10:41:30 +01:00
document : & DocumentNode ,
2021-07-26 00:08:08 +02:00
frames : & [ Rc < Frame > ] ,
) -> bool {
let mut ok = true ;
2021-06-27 12:28:40 +02:00
2021-07-26 00:08:08 +02: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 12:28:40 +02:00
}
2021-07-20 20:21:56 +02:00
ctx . layouts . turnaround ( ) ;
2021-06-27 12:28:40 +02:00
2021-11-29 23:22:53 +01:00
let cached = silenced ( | | layout ( ctx , document ) ) ;
2021-07-26 00:08:08 +02:00
let misses = ctx
. layouts
. entries ( )
. filter ( | e | e . level ( ) = = level & & ! e . hit ( ) & & e . age ( ) = = 2 )
. count ( ) ;
if misses > 0 {
println! (
2021-09-27 11:40:28 +02:00
" Subtest {} relayout had {} cache misses on level {} of {} ❌ " ,
i ,
misses ,
level ,
reference . levels ( ) - 1 ,
2021-07-26 00:08:08 +02:00
) ;
ok = false ;
}
if cached ! = frames {
println! ( " Subtest {} relayout differs from clean pass ❌ " , i ) ;
ok = false ;
}
2021-06-27 18:06:39 +02:00
}
2021-07-26 00:08:08 +02:00
ctx . layouts = reference ;
ctx . layouts . turnaround ( ) ;
ok
2020-12-10 22:44:35 +01:00
}
2021-07-31 22:59:14 +02:00
fn parse_metadata ( source : & SourceFile ) -> ( Option < bool > , Vec < Error > ) {
2021-01-31 22:43:11 +01:00
let mut compare_ref = None ;
2021-07-30 18:04:08 +02:00
let mut errors = vec! [ ] ;
2020-12-10 22:44:35 +01:00
2021-07-31 22:59:14 +02:00
let lines : Vec < _ > = source . src ( ) . lines ( ) . map ( str ::trim ) . collect ( ) ;
2021-07-09 10:50:25 +02:00
for ( i , line ) in lines . iter ( ) . enumerate ( ) {
2021-01-31 22:43:11 +01:00
if line . starts_with ( " // Ref: false " ) {
compare_ref = Some ( false ) ;
}
if line . starts_with ( " // Ref: true " ) {
compare_ref = Some ( true ) ;
}
2021-01-13 14:07:38 +01:00
2021-07-30 18:04:08 +02:00
let rest = if let Some ( rest ) = line . strip_prefix ( " // Error: " ) {
rest
2020-12-10 22:44:35 +01:00
} else {
continue ;
} ;
2021-07-31 22:59:14 +02:00
fn num ( s : & mut Scanner ) -> usize {
2021-01-13 17:22:33 +01:00
s . eat_while ( | c | c . is_numeric ( ) ) . parse ( ) . unwrap ( )
}
2020-12-10 22:44:35 +01:00
2021-07-09 10:50:25 +02:00
let comments =
lines [ i .. ] . iter ( ) . take_while ( | line | line . starts_with ( " // " ) ) . count ( ) ;
2021-11-02 12:13:45 +01:00
let pos = | s : & mut Scanner | -> usize {
2021-07-31 22:59:14 +02:00
let first = num ( s ) - 1 ;
2021-01-30 16:46:16 +01:00
let ( delta , column ) =
2021-07-31 22:59:14 +02:00
if s . eat_if ( ':' ) { ( first , num ( s ) - 1 ) } else { ( 0 , first ) } ;
let line = ( i + comments ) + delta ;
2021-11-02 12:13:45 +01:00
source . line_column_to_byte ( line , column ) . unwrap ( )
2021-01-13 17:22:33 +01:00
} ;
2021-01-13 14:07:38 +01:00
2021-01-13 17:22:33 +01:00
let mut s = Scanner ::new ( rest ) ;
2021-02-17 21:30:20 +01:00
let start = pos ( & mut s ) ;
let end = if s . eat_if ( '-' ) { pos ( & mut s ) } else { start } ;
2021-08-13 12:21:14 +02:00
let span = Span ::new ( source . id ( ) , start , end ) ;
2021-02-17 21:30:20 +01:00
2021-08-13 12:21:14 +02:00
errors . push ( Error ::new ( span , s . rest ( ) . trim ( ) ) ) ;
2021-01-13 17:22:33 +01:00
}
2020-12-10 22:44:35 +01:00
2021-07-30 18:04:08 +02:00
( compare_ref , errors )
}
2021-07-31 22:59:14 +02:00
fn print_error ( source : & SourceFile , line : usize , error : & Error ) {
2021-11-02 12:13:45 +01: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 22:59:14 +02:00
println! (
" Error: {}:{}-{}:{}: {} " ,
start_line , start_col , end_line , end_col , error . message
) ;
2020-12-10 22:44:35 +01:00
}
2020-08-02 21:17:42 +02:00
2021-08-27 13:29:50 +02:00
fn draw ( ctx : & Context , frames : & [ Rc < Frame > ] , dpp : f32 ) -> sk ::Pixmap {
2020-11-25 18:46:47 +01:00
let pad = Length ::pt ( 5.0 ) ;
2021-11-26 16:32:06 +01: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 22:18:55 +02:00
2021-11-20 15:51:07 +01:00
let pxw = ( dpp * width . to_f32 ( ) ) as u32 ;
let pxh = ( dpp * height . to_f32 ( ) ) as u32 ;
2021-11-16 21:32:29 +01:00
if pxw > 4000 | | pxh > 4000 {
2021-03-12 13:57:47 +01:00
panic! (
2021-08-31 16:25:12 +02:00
" overlarge image: {} by {} ({:?} x {:?}) " ,
2021-11-16 21:32:29 +01:00
pxw , pxh , width , height ,
2021-03-12 13:57:47 +01:00
) ;
2021-01-13 23:19:44 +01:00
}
2021-11-16 21:32:29 +01:00
let mut canvas = sk ::Pixmap ::new ( pxw , pxh ) . unwrap ( ) ;
2021-07-08 22:33:44 +02:00
canvas . fill ( sk ::Color ::BLACK ) ;
2020-08-02 21:17:42 +02:00
2021-11-16 21:32:29 +01: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 15:51:07 +01:00
let mut ts =
sk ::Transform ::from_scale ( dpp , dpp ) . pre_translate ( pad . to_f32 ( ) , pad . to_f32 ( ) ) ;
2021-11-16 21:32:29 +01:00
2021-01-03 00:12:09 +01:00
for frame in frames {
2021-11-16 21:32:29 +01:00
let mut background = sk ::Paint ::default ( ) ;
background . set_color ( sk ::Color ::WHITE ) ;
2020-08-02 21:17:42 +02:00
2021-11-26 16:32:06 +01:00
let w = frame . size . x . to_f32 ( ) ;
let h = frame . size . y . to_f32 ( ) ;
2021-11-16 21:32:29 +01:00
let rect = sk ::Rect ::from_xywh ( 0.0 , 0.0 , w , h ) . unwrap ( ) ;
canvas . fill_rect ( rect , & background , ts , None ) ;
2020-08-02 21:17:42 +02:00
2021-11-16 21:32:29 +01:00
draw_frame ( & mut canvas , ts , & mask , ctx , frame ) ;
2021-11-26 16:32:06 +01:00
ts = ts . pre_translate ( 0.0 , ( frame . size . y + pad ) . to_f32 ( ) ) ;
2020-08-02 21:17:42 +02:00
}
2020-11-25 18:46:47 +01:00
canvas
2020-08-02 21:17:42 +02:00
}
2021-11-16 21:32:29 +01:00
fn draw_frame (
canvas : & mut sk ::Pixmap ,
ts : sk ::Transform ,
mask : & sk ::ClipMask ,
ctx : & Context ,
frame : & Frame ,
) {
for ( pos , element ) in & frame . elements {
2021-11-20 15:51:07 +01:00
let x = pos . x . to_f32 ( ) ;
let y = pos . y . to_f32 ( ) ;
2021-11-16 21:32:29 +01:00
let ts = ts . pre_translate ( x , y ) ;
match * element {
2021-11-22 15:26:56 +01:00
Element ::Group ( ref group ) = > {
draw_group ( canvas , ts , mask , ctx , group ) ;
}
2021-11-16 21:32:29 +01:00
Element ::Text ( ref text ) = > {
draw_text ( canvas , ts , mask , ctx . fonts . get ( text . face_id ) , text ) ;
}
2021-11-20 15:51:07 +01:00
Element ::Shape ( ref shape ) = > {
draw_shape ( canvas , ts , mask , shape ) ;
2021-11-16 21:32:29 +01:00
}
Element ::Image ( id , size ) = > {
draw_image ( canvas , ts , mask , ctx . images . get ( id ) , size ) ;
}
Element ::Link ( _ , s ) = > {
2021-11-20 15:51:07 +01: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 21:32:29 +01:00
}
}
}
}
2021-11-22 15:26:56 +01:00
fn draw_group (
canvas : & mut sk ::Pixmap ,
ts : sk ::Transform ,
mask : & sk ::ClipMask ,
ctx : & Context ,
group : & Group ,
) {
2021-11-23 22:04:08 +01:00
let ts = ts . pre_concat ( convert_typst_transform ( group . transform ) ) ;
2021-11-22 15:26:56 +01:00
if group . clips {
2021-11-26 16:32:06 +01:00
let w = group . frame . size . x . to_f32 ( ) ;
let h = group . frame . size . y . to_f32 ( ) ;
2021-11-22 15:26:56 +01:00
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 ( ) ;
let mut mask = mask . clone ( ) ;
if mask . intersect_path ( & path , rule , false ) . is_none ( ) {
// Fails if clipping rect is empty. In that case we just clip everything
// by returning.
return ;
}
draw_frame ( canvas , ts , & mask , ctx , & group . frame ) ;
} else {
draw_frame ( canvas , ts , mask , ctx , & group . frame ) ;
} ;
}
2021-11-16 21:32:29 +01:00
fn draw_text (
canvas : & mut sk ::Pixmap ,
ts : sk ::Transform ,
mask : & sk ::ClipMask ,
face : & Face ,
text : & Text ,
) {
2021-08-27 13:29:50 +02:00
let ttf = face . ttf ( ) ;
2021-11-20 15:51:07 +01: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 13:29:50 +02:00
let scale = size / units_per_em ;
2020-08-02 21:17:42 +02:00
2021-08-27 13:29:50 +02:00
let mut x = 0.0 ;
2021-05-14 11:14:28 +02:00
for glyph in & text . glyphs {
2021-08-27 13:29:50 +02:00
let glyph_id = GlyphId ( glyph . id ) ;
2021-11-20 15:51:07 +01:00
let offset = x + glyph . x_offset . to_length ( text . size ) . to_f32 ( ) ;
2021-08-27 13:29:50 +02:00
let ts = ts . pre_translate ( offset , 0.0 ) ;
2020-11-25 18:46:47 +01:00
2021-03-24 17:12:34 +01:00
if let Some ( tree ) = ttf
2021-08-27 13:29:50 +02:00
. glyph_svg_image ( glyph_id )
2021-03-23 15:32:36 +01: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 14:58:57 +01:00
. and_then ( | s | {
usvg ::Tree ::from_str ( & s , & usvg ::Options ::default ( ) . to_ref ( ) ) . ok ( )
} )
2021-03-23 15:32:36 +01:00
{
for child in tree . root ( ) . children ( ) {
if let usvg ::NodeKind ::Path ( node ) = & * child . borrow ( ) {
2021-08-27 13:29:50 +02:00
// SVG is already Y-down, no flipping required.
2021-03-24 17:12:34 +01:00
let ts = convert_usvg_transform ( node . transform )
2021-08-27 13:29:50 +02:00
. post_scale ( scale , scale )
2021-03-23 15:32:36 +01:00
. post_concat ( ts ) ;
2021-08-27 13:29:50 +02:00
2021-03-23 15:32:36 +01:00
if let Some ( fill ) = & node . fill {
2021-08-27 13:29:50 +02:00
let path = convert_usvg_path ( & node . data ) ;
2021-03-23 15:32:36 +01:00
let ( paint , fill_rule ) = convert_usvg_fill ( fill ) ;
2021-11-16 21:32:29 +01:00
canvas . fill_path ( & path , & paint , fill_rule , ts , Some ( mask ) ) ;
2021-03-23 15:32:36 +01:00
}
}
}
2021-08-27 13:29:50 +02: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 14:58:57 +01:00
let img = RasterImage ::parse ( & raster . data ) . unwrap ( ) ;
2021-08-27 13:29:50 +02: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 14:58:57 +01:00
draw_image ( canvas , ts , mask , & Image ::Raster ( img ) , Size ::new ( w , h ) ) ;
2021-04-05 22:32:09 +02:00
} else {
// Otherwise, draw normal outline.
2021-07-08 22:33:44 +02:00
let mut builder = WrappedPathBuilder ( sk ::PathBuilder ::new ( ) ) ;
2021-08-27 13:29:50 +02:00
if ttf . outline_glyph ( glyph_id , & mut builder ) . is_some ( ) {
2021-11-20 15:51:07 +01:00
// Flip vertically because font design coordinate system is Y-up.
2021-08-27 13:29:50 +02:00
let ts = ts . pre_scale ( scale , - scale ) ;
2021-04-05 22:32:09 +02:00
let path = builder . 0. finish ( ) . unwrap ( ) ;
2021-11-20 15:51:07 +01:00
let paint = convert_typst_paint ( text . fill ) ;
2021-11-16 21:32:29 +01:00
canvas . fill_path ( & path , & paint , sk ::FillRule ::default ( ) , ts , Some ( mask ) ) ;
2021-04-05 22:32:09 +02:00
}
2021-03-23 15:32:36 +01:00
}
2020-08-02 21:17:42 +02:00
2021-11-20 15:51:07 +01:00
x + = glyph . x_advance . to_length ( text . size ) . to_f32 ( ) ;
2020-08-02 21:17:42 +02:00
}
}
2021-11-20 15:51:07 +01:00
fn draw_shape (
2021-07-08 22:33:44 +02:00
canvas : & mut sk ::Pixmap ,
ts : sk ::Transform ,
2021-11-16 21:32:29 +01:00
mask : & sk ::ClipMask ,
2021-11-20 15:51:07 +01:00
shape : & Shape ,
2021-07-08 22:33:44 +02:00
) {
2021-11-20 15:51:07 +01:00
let path = match shape . geometry {
Geometry ::Rect ( size ) = > {
2021-11-26 16:32:06 +01:00
let w = size . x . to_f32 ( ) ;
let h = size . y . to_f32 ( ) ;
2021-07-08 22:33:44 +02:00
let rect = sk ::Rect ::from_xywh ( 0.0 , 0.0 , w , h ) . unwrap ( ) ;
2021-11-20 15:51:07 +01:00
sk ::PathBuilder ::from_rect ( rect )
2021-03-20 20:19:30 +01:00
}
2021-07-08 22:33:44 +02:00
Geometry ::Ellipse ( size ) = > {
2021-11-20 15:51:07 +01:00
let approx = geom ::Path ::ellipse ( size ) ;
convert_typst_path ( & approx )
2021-06-10 23:08:52 +02:00
}
2021-11-20 15:51:07 +01: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 12:54:44 +01:00
}
2021-11-20 15:51:07 +01:00
Geometry ::Path ( ref path ) = > convert_typst_path ( path ) ,
2021-02-06 12:30:44 +01:00
} ;
2021-11-20 15:51:07 +01: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 21:30:18 +01:00
}
2021-11-16 21:32:29 +01:00
fn draw_image (
canvas : & mut sk ::Pixmap ,
ts : sk ::Transform ,
mask : & sk ::ClipMask ,
img : & Image ,
size : Size ,
) {
2021-11-26 16:32:06 +01:00
let view_width = size . x . to_f32 ( ) ;
let view_height = size . y . to_f32 ( ) ;
2021-12-06 14:58:57 +01: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 18:46:47 +01:00
2021-07-08 22:33:44 +02:00
let mut paint = sk ::Paint ::default ( ) ;
paint . shader = sk ::Pattern ::new (
2021-03-23 13:17:00 +01:00
pixmap . as_ref ( ) ,
2021-07-08 22:33:44 +02:00
sk ::SpreadMode ::Pad ,
sk ::FilterQuality ::Bilinear ,
2020-11-25 18:46:47 +01:00
1.0 ,
2021-08-27 13:29:50 +02:00
sk ::Transform ::from_scale ( scale_x , scale_y ) ,
2020-11-25 18:46:47 +01:00
) ;
2021-07-08 22:33:44 +02:00
let rect = sk ::Rect ::from_xywh ( 0.0 , 0.0 , view_width , view_height ) . unwrap ( ) ;
2021-11-16 21:32:29 +01:00
canvas . fill_rect ( rect , & paint , ts , Some ( mask ) ) ;
2020-11-20 16:36:22 +01:00
}
2021-11-23 22:04:08 +01: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 22:33:44 +02:00
fn convert_typst_paint ( paint : Paint ) -> sk ::Paint < 'static > {
2021-11-20 15:51:07 +01:00
let Paint ::Solid ( Color ::Rgba ( c ) ) = paint ;
2021-07-08 22:33:44 +02:00
let mut paint = sk ::Paint ::default ( ) ;
paint . set_color_rgba8 ( c . r , c . g , c . b , c . a ) ;
2021-11-20 15:51:07 +01:00
paint . anti_alias = true ;
2021-03-20 20:19:30 +01:00
paint
}
2021-07-08 22:33:44 +02:00
fn convert_typst_path ( path : & geom ::Path ) -> sk ::Path {
let mut builder = sk ::PathBuilder ::new ( ) ;
2021-03-20 20:19:30 +01:00
for elem in & path . 0 {
match elem {
2021-07-08 22:33:44 +02:00
PathElement ::MoveTo ( p ) = > {
2021-11-20 15:51:07 +01:00
builder . move_to ( p . x . to_f32 ( ) , p . y . to_f32 ( ) ) ;
2021-05-05 22:18:33 +02:00
}
2021-07-08 22:33:44 +02:00
PathElement ::LineTo ( p ) = > {
2021-11-20 15:51:07 +01:00
builder . line_to ( p . x . to_f32 ( ) , p . y . to_f32 ( ) ) ;
2021-05-05 22:18:33 +02:00
}
2021-07-08 22:33:44 +02:00
PathElement ::CubicTo ( p1 , p2 , p3 ) = > {
2021-11-20 15:51:07 +01: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 22:18:33 +02:00
}
2021-07-08 22:33:44 +02:00
PathElement ::ClosePath = > {
2021-05-05 22:18:33 +02:00
builder . close ( ) ;
2021-03-23 15:32:36 +01:00
}
2021-03-20 20:19:30 +01:00
} ;
}
builder . finish ( ) . unwrap ( )
}
2021-07-08 22:33:44 +02:00
fn convert_usvg_transform ( transform : usvg ::Transform ) -> sk ::Transform {
2021-05-05 22:18:33 +02:00
let usvg ::Transform { a , b , c , d , e , f } = transform ;
2021-11-20 15:51:07 +01:00
sk ::Transform ::from_row ( a as _ , b as _ , c as _ , d as _ , e as _ , f as _ )
2021-05-05 22:18:33 +02:00
}
2021-07-08 22:33:44 +02:00
fn convert_usvg_fill ( fill : & usvg ::Fill ) -> ( sk ::Paint < 'static > , sk ::FillRule ) {
let mut paint = sk ::Paint ::default ( ) ;
2021-03-23 15:32:36 +01:00
paint . anti_alias = true ;
2021-12-06 14:58:57 +01:00
if let usvg ::Paint ::Color ( usvg ::Color { red , green , blue , alpha : _ } ) = fill . paint {
2021-11-20 15:51:07 +01:00
paint . set_color_rgba8 ( red , green , blue , fill . opacity . to_u8 ( ) )
2021-03-23 15:32:36 +01:00
}
let rule = match fill . rule {
2021-07-08 22:33:44 +02:00
usvg ::FillRule ::NonZero = > sk ::FillRule ::Winding ,
usvg ::FillRule ::EvenOdd = > sk ::FillRule ::EvenOdd ,
2021-03-23 15:32:36 +01:00
} ;
( paint , rule )
}
2021-07-08 22:33:44 +02:00
fn convert_usvg_path ( path : & usvg ::PathData ) -> sk ::Path {
let mut builder = sk ::PathBuilder ::new ( ) ;
2021-03-23 15:32:36 +01:00
for seg in path . iter ( ) {
match * seg {
2021-05-05 22:18:33 +02:00
usvg ::PathSegment ::MoveTo { x , y } = > {
2021-11-20 15:51:07 +01:00
builder . move_to ( x as _ , y as _ ) ;
2021-05-05 22:18:33 +02:00
}
2021-03-23 15:32:36 +01:00
usvg ::PathSegment ::LineTo { x , y } = > {
2021-11-20 15:51:07 +01:00
builder . line_to ( x as _ , y as _ ) ;
2021-03-23 15:32:36 +01:00
}
usvg ::PathSegment ::CurveTo { x1 , y1 , x2 , y2 , x , y } = > {
2021-11-20 15:51:07 +01:00
builder . cubic_to ( x1 as _ , y1 as _ , x2 as _ , y2 as _ , x as _ , y as _ ) ;
2021-05-05 22:18:33 +02:00
}
usvg ::PathSegment ::ClosePath = > {
builder . close ( ) ;
2021-03-23 15:32:36 +01:00
}
}
}
builder . finish ( ) . unwrap ( )
}
2021-07-08 22:33:44 +02:00
struct WrappedPathBuilder ( sk ::PathBuilder ) ;
2020-08-02 21:17:42 +02: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 15:51:07 +01: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-29 23:22:53 +01:00
/// Disable stdout and stderr during execution of `f`.
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
}