2020-08-02 21:17:42 +02:00
use std ::env ;
use std ::ffi ::OsStr ;
2020-12-10 22:44:35 +01:00
use std ::fs ;
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
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-01-14 16:47:29 +01:00
use walkdir ::WalkDir ;
2020-08-02 21:17:42 +02:00
2021-07-08 22:33:44 +02:00
use typst ::color ::Color ;
2021-07-30 18:04:08 +02:00
use typst ::diag ::{ Error , TypResult } ;
use typst ::eval ::{ eval , Value } ;
2021-06-27 18:06:39 +02:00
use typst ::exec ::{ exec , State } ;
2021-07-08 22:33:44 +02:00
use typst ::geom ::{ self , Length , PathElement , Point , Sides , Size } ;
2021-05-28 12:44:44 +02:00
use typst ::image ::ImageId ;
2021-07-30 18:04:08 +02:00
use typst ::layout ::{ layout , Element , Frame , Geometry , LayoutTree , Paint , Text } ;
2021-08-09 11:06:37 +02:00
use typst ::loading ::FsLoader ;
2021-07-31 22:59:14 +02:00
use typst ::parse ::{ parse , Scanner } ;
2021-08-09 11:06:37 +02:00
use typst ::source ::{ SourceFile , SourceId } ;
2021-07-31 22:59:14 +02:00
use typst ::syntax ::Pos ;
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.
let mut state = State ::default ( ) ;
2021-07-29 13:21:25 +02:00
let page = state . page_mut ( ) ;
page . size = Size ::new ( Length ::pt ( 120.0 ) , Length ::inf ( ) ) ;
page . margins = Sides ::splat ( Some ( Length ::pt ( 10.0 ) . into ( ) ) ) ;
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 {
typst ::bail! (
2021-08-09 11:06:37 +02:00
args . source ,
2021-07-30 18:04:08 +02:00
args . span ,
" Assertion failed: {:?} != {:?} " ,
lhs ,
rhs
) ;
}
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-08-09 11:06:37 +02:00
let mut ctx = Context ::builder ( ) . std ( std ) . state ( state ) . 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-06-18 13:00:36 +02:00
2021-08-09 11:06:37 +02:00
let result = typeset ( ctx , id ) ;
2021-07-30 18:04:08 +02:00
let ( frames , mut errors ) = match result {
#[ allow(unused_variables) ]
Ok ( ( tree , mut frames ) ) = > {
#[ cfg(feature = " layout-cache " ) ]
( ok & = test_incremental ( ctx , i , & tree , & 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-09 11:06:37 +02:00
errors . retain ( | error | error . source = = id ) ;
2021-07-31 22:59:14 +02:00
for error in & mut errors {
error . trace . clear ( ) ;
}
2021-07-30 18:04:08 +02:00
ref_errors . sort ( ) ;
errors . sort ( ) ;
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-09 11:06:37 +02:00
if error . 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-08-09 11:06:37 +02:00
fn typeset ( ctx : & mut Context , id : SourceId ) -> TypResult < ( LayoutTree , Vec < Rc < Frame > > ) > {
let source = ctx . sources . get ( id ) ;
let ast = parse ( source ) ? ;
let module = eval ( ctx , id , Rc ::new ( ast ) ) ? ;
let tree = exec ( ctx , & module . template ) ;
let frames = layout ( ctx , & tree ) ;
Ok ( ( tree , frames ) )
}
2021-07-26 00:08:08 +02:00
#[ cfg(feature = " layout-cache " ) ]
fn test_incremental (
ctx : & mut Context ,
i : usize ,
tree : & LayoutTree ,
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-07-26 00:08:08 +02:00
let cached = layout ( ctx , tree ) ;
let misses = ctx
. layouts
. entries ( )
. filter ( | e | e . level ( ) = = level & & ! e . hit ( ) & & e . age ( ) = = 2 )
. count ( ) ;
if misses > 0 {
println! (
" Subtest {} relayout had {} cache misses on level {} ❌ " ,
i , misses , level
) ;
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-01-13 17:22:33 +01:00
let pos = | s : & mut Scanner | -> Pos {
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 ;
source . line_column_to_pos ( 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-09 11:06:37 +02:00
errors . push ( Error ::new ( source . id ( ) , start .. end , 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-08-09 11:06:37 +02:00
let start_line = 1 + line + source . pos_to_line ( error . span . start ) . unwrap ( ) ;
let start_col = 1 + source . pos_to_column ( error . span . start ) . unwrap ( ) ;
let end_line = 1 + line + source . pos_to_line ( error . span . end ) . unwrap ( ) ;
let end_col = 1 + source . pos_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-07-20 20:21:56 +02:00
fn draw ( ctx : & Context , frames : & [ Rc < Frame > ] , dpi : f32 ) -> sk ::Pixmap {
2020-11-25 18:46:47 +01:00
let pad = Length ::pt ( 5.0 ) ;
2021-01-03 00:12:09 +01:00
let height = pad + frames . iter ( ) . map ( | l | l . size . height + pad ) . sum ::< Length > ( ) ;
2021-06-12 18:24:31 +02:00
let width = 2.0 * pad + frames . iter ( ) . map ( | l | l . size . width ) . max ( ) . unwrap_or_default ( ) ;
2020-08-30 22:18:55 +02:00
2021-05-05 22:18:33 +02:00
let pixel_width = ( dpi * width . to_pt ( ) as f32 ) as u32 ;
let pixel_height = ( dpi * height . to_pt ( ) as f32 ) as u32 ;
2021-01-13 23:19:44 +01:00
if pixel_width > 4000 | | pixel_height > 4000 {
2021-03-12 13:57:47 +01:00
panic! (
" overlarge image: {} by {} ({} x {}) " ,
pixel_width , pixel_height , width , height ,
) ;
2021-01-13 23:19:44 +01:00
}
2021-07-08 22:33:44 +02:00
let mut canvas = sk ::Pixmap ::new ( pixel_width , pixel_height ) . unwrap ( ) ;
let ts = sk ::Transform ::from_scale ( dpi , dpi ) ;
canvas . fill ( sk ::Color ::BLACK ) ;
2020-08-02 21:17:42 +02:00
2021-05-17 22:55:31 +02:00
let mut origin = Point ::splat ( pad ) ;
2021-01-03 00:12:09 +01:00
for frame in frames {
2021-07-08 22:33:44 +02:00
let mut paint = sk ::Paint ::default ( ) ;
paint . set_color ( sk ::Color ::WHITE ) ;
2020-11-25 18:46:47 +01:00
canvas . fill_rect (
2021-07-08 22:33:44 +02:00
sk ::Rect ::from_xywh (
2020-11-25 18:46:47 +01:00
origin . x . to_pt ( ) as f32 ,
origin . y . to_pt ( ) as f32 ,
2021-01-03 00:12:09 +01:00
frame . size . width . to_pt ( ) as f32 ,
frame . size . height . to_pt ( ) as f32 ,
2020-11-25 18:46:47 +01:00
)
. unwrap ( ) ,
& paint ,
2021-03-23 13:17:00 +01:00
ts ,
None ,
2020-08-02 21:17:42 +02:00
) ;
2021-06-18 13:01:55 +02:00
for ( pos , element ) in frame . elements ( ) {
let global = origin + pos ;
2021-05-14 11:14:28 +02:00
let x = global . x . to_pt ( ) as f32 ;
let y = global . y . to_pt ( ) as f32 ;
2021-03-24 17:12:34 +01:00
let ts = ts . pre_translate ( x , y ) ;
2021-05-14 11:14:28 +02:00
match * element {
Element ::Text ( ref text ) = > {
2021-07-20 20:21:56 +02:00
draw_text ( & mut canvas , ts , ctx , text ) ;
2021-05-14 11:14:28 +02:00
}
2021-07-08 22:33:44 +02:00
Element ::Geometry ( ref geometry , paint ) = > {
draw_geometry ( & mut canvas , ts , geometry , paint ) ;
2021-05-14 11:14:28 +02:00
}
Element ::Image ( id , size ) = > {
2021-07-20 20:21:56 +02:00
draw_image ( & mut canvas , ts , ctx , id , size ) ;
2021-05-14 11:14:28 +02:00
}
2020-08-02 21:17:42 +02:00
}
}
2021-01-03 00:12:09 +01:00
origin . y + = frame . size . height + pad ;
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-07-20 20:21:56 +02:00
fn draw_text ( canvas : & mut sk ::Pixmap , ts : sk ::Transform , ctx : & Context , text : & Text ) {
let ttf = ctx . fonts . get ( text . face_id ) . ttf ( ) ;
2021-04-05 22:32:09 +02:00
let mut x = 0.0 ;
2020-08-02 21:17:42 +02:00
2021-05-14 11:14:28 +02:00
for glyph in & text . glyphs {
2021-04-06 15:13:20 +02:00
let units_per_em = ttf . units_per_em ( ) ;
2021-05-14 11:14:28 +02:00
let s = text . size . to_pt ( ) as f32 / units_per_em as f32 ;
2021-04-05 22:32:09 +02:00
let dx = glyph . x_offset . to_pt ( ) as f32 ;
let ts = ts . pre_translate ( x + dx , 0.0 ) ;
2020-11-25 18:46:47 +01:00
2021-03-23 15:32:36 +01:00
// Try drawing SVG if present.
2021-03-24 17:12:34 +01:00
if let Some ( tree ) = ttf
2021-04-21 21:17:25 +02:00
. glyph_svg_image ( GlyphId ( 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 )
} )
. and_then ( | s | usvg ::Tree ::from_str ( & s , & usvg ::Options ::default ( ) ) . ok ( ) )
{
for child in tree . root ( ) . children ( ) {
if let usvg ::NodeKind ::Path ( node ) = & * child . borrow ( ) {
let path = convert_usvg_path ( & node . data ) ;
2021-03-24 17:12:34 +01:00
let ts = convert_usvg_transform ( node . transform )
. post_scale ( s , s )
2021-03-23 15:32:36 +01:00
. post_concat ( ts ) ;
if let Some ( fill ) = & node . fill {
let ( paint , fill_rule ) = convert_usvg_fill ( fill ) ;
canvas . fill_path ( & path , & paint , fill_rule , ts , None ) ;
}
}
}
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-04-21 21:17:25 +02:00
if ttf . outline_glyph ( GlyphId ( glyph . id ) , & mut builder ) . is_some ( ) {
2021-04-05 22:32:09 +02:00
let path = builder . 0. finish ( ) . unwrap ( ) ;
let ts = ts . pre_scale ( s , - s ) ;
2021-07-08 22:33:44 +02:00
let mut paint = convert_typst_paint ( text . fill ) ;
2021-04-05 22:32:09 +02:00
paint . anti_alias = true ;
2021-07-08 22:33:44 +02:00
canvas . fill_path ( & path , & paint , sk ::FillRule ::default ( ) , ts , None ) ;
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-04-05 22:32:09 +02:00
x + = glyph . x_advance . to_pt ( ) as f32 ;
2020-08-02 21:17:42 +02:00
}
}
2021-07-08 22:33:44 +02:00
fn draw_geometry (
canvas : & mut sk ::Pixmap ,
ts : sk ::Transform ,
geometry : & Geometry ,
paint : Paint ,
) {
let paint = convert_typst_paint ( paint ) ;
let rule = sk ::FillRule ::default ( ) ;
2021-02-04 21:30:18 +01:00
2021-07-08 22:33:44 +02:00
match * geometry {
Geometry ::Rect ( Size { width , height } ) = > {
2021-03-20 20:19:30 +01:00
let w = width . to_pt ( ) as f32 ;
let h = height . to_pt ( ) as f32 ;
2021-07-08 22:33:44 +02:00
let rect = sk ::Rect ::from_xywh ( 0.0 , 0.0 , w , h ) . unwrap ( ) ;
2021-03-23 13:17:00 +01:00
canvas . fill_rect ( rect , & paint , ts , None ) ;
2021-03-20 20:19:30 +01:00
}
2021-07-08 22:33:44 +02:00
Geometry ::Ellipse ( size ) = > {
2021-05-17 22:55:31 +02:00
let path = convert_typst_path ( & geom ::Path ::ellipse ( size ) ) ;
2021-03-23 13:17:00 +01:00
canvas . fill_path ( & path , & paint , rule , ts , None ) ;
2021-03-20 20:19:30 +01:00
}
2021-07-08 22:33:44 +02:00
Geometry ::Line ( target , thickness ) = > {
2021-06-10 23:08:52 +02:00
let path = {
2021-07-08 22:33:44 +02:00
let mut builder = sk ::PathBuilder ::new ( ) ;
2021-06-10 23:08:52 +02:00
builder . line_to ( target . x . to_pt ( ) as f32 , target . y . to_pt ( ) as f32 ) ;
builder . finish ( ) . unwrap ( )
} ;
2021-07-08 22:33:44 +02:00
let mut stroke = sk ::Stroke ::default ( ) ;
2021-06-10 23:08:52 +02:00
stroke . width = thickness . to_pt ( ) as f32 ;
canvas . stroke_path ( & path , & paint , & stroke , ts , None ) ;
}
2021-07-08 22:33:44 +02:00
Geometry ::Path ( ref path ) = > {
2021-03-23 15:32:36 +01:00
let path = convert_typst_path ( path ) ;
2021-03-23 13:17:00 +01:00
canvas . fill_path ( & path , & paint , rule , ts , None ) ;
2021-02-06 12:54:44 +01:00
}
2021-02-06 12:30:44 +01:00
} ;
2021-02-04 21:30:18 +01:00
}
2021-05-28 12:44:44 +02:00
fn draw_image (
2021-07-08 22:33:44 +02:00
canvas : & mut sk ::Pixmap ,
ts : sk ::Transform ,
2021-07-20 20:21:56 +02:00
ctx : & Context ,
2021-05-28 12:44:44 +02:00
id : ImageId ,
size : Size ,
) {
2021-07-20 20:21:56 +02:00
let img = ctx . images . get ( id ) ;
2020-11-27 22:35:42 +01:00
2021-07-08 22:33:44 +02:00
let mut pixmap = sk ::Pixmap ::new ( img . buf . width ( ) , img . buf . height ( ) ) . unwrap ( ) ;
2021-01-01 17:54:31 +01:00
for ( ( _ , _ , src ) , dest ) in img . buf . pixels ( ) . zip ( pixmap . pixels_mut ( ) ) {
2020-11-27 22:35:42 +01:00
let Rgba ( [ r , g , b , a ] ) = src ;
2021-07-08 22:33:44 +02:00
* dest = sk ::ColorU8 ::from_rgba ( r , g , b , a ) . premultiply ( ) ;
2020-11-20 16:36:22 +01:00
}
2021-05-14 11:14:28 +02:00
let view_width = size . width . to_pt ( ) as f32 ;
let view_height = size . height . to_pt ( ) as f32 ;
2020-11-25 18:46:47 +01:00
let scale_x = view_width as f32 / pixmap . width ( ) as f32 ;
let scale_y = view_height as f32 / pixmap . height ( ) as f32 ;
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-07-08 22:33:44 +02:00
sk ::Transform ::from_row ( scale_x , 0.0 , 0.0 , scale_y , 0.0 , 0.0 ) ,
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-03-23 13:17:00 +01:00
canvas . fill_rect ( rect , & paint , ts , None ) ;
2020-11-20 16:36:22 +01:00
}
2021-07-08 22:33:44 +02:00
fn convert_typst_paint ( paint : Paint ) -> sk ::Paint < 'static > {
let Paint ::Color ( Color ::Rgba ( c ) ) = paint ;
let mut paint = sk ::Paint ::default ( ) ;
paint . set_color_rgba8 ( c . r , c . g , c . b , c . a ) ;
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-05-05 22:18:33 +02:00
let f = | v : Length | v . to_pt ( ) as f32 ;
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-05-05 22:18:33 +02:00
builder . move_to ( f ( p . x ) , f ( p . y ) ) ;
}
2021-07-08 22:33:44 +02:00
PathElement ::LineTo ( p ) = > {
2021-05-05 22:18:33 +02:00
builder . line_to ( f ( p . x ) , f ( p . y ) ) ;
}
2021-07-08 22:33:44 +02:00
PathElement ::CubicTo ( p1 , p2 , p3 ) = > {
2021-05-05 22:18:33 +02:00
builder . cubic_to ( f ( p1 . x ) , f ( p1 . y ) , f ( p2 . x ) , f ( p2 . y ) , f ( p3 . x ) , f ( p3 . y ) ) ;
}
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 g = | v : f64 | v as f32 ;
let usvg ::Transform { a , b , c , d , e , f } = transform ;
2021-07-08 22:33:44 +02:00
sk ::Transform ::from_row ( g ( a ) , g ( b ) , g ( c ) , g ( d ) , g ( e ) , g ( f ) )
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 ;
match fill . paint {
2021-05-05 22:18:33 +02:00
usvg ::Paint ::Color ( usvg ::Color { red , green , blue } ) = > {
paint . set_color_rgba8 ( red , green , blue , fill . opacity . to_u8 ( ) )
}
2021-03-23 15:32:36 +01:00
usvg ::Paint ::Link ( _ ) = > { }
}
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-05-05 22:18:33 +02:00
let f = | v : f64 | v as f32 ;
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 } = > {
builder . move_to ( f ( x ) , f ( y ) ) ;
}
2021-03-23 15:32:36 +01:00
usvg ::PathSegment ::LineTo { x , y } = > {
builder . line_to ( f ( x ) , f ( y ) ) ;
}
usvg ::PathSegment ::CurveTo { x1 , y1 , x2 , y2 , x , y } = > {
2021-05-05 22:18:33 +02:00
builder . cubic_to ( f ( x1 ) , f ( y1 ) , f ( x2 ) , f ( y2 ) , f ( x ) , f ( y ) ) ;
}
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 ( ) ;
}
}