@@ -5,40 +5,16 @@ mod formatting;
55
66pub use error:: OgImageError ;
77
8- use crate :: formatting:: { format_bytes , format_number } ;
8+ use crate :: formatting:: { serialize_bytes , serialize_number , serialize_optional_number } ;
99use bytes:: Bytes ;
1010use crates_io_env_vars:: var;
11- use minijinja:: { Environment , context} ;
1211use serde:: Serialize ;
1312use std:: collections:: HashMap ;
1413use std:: path:: PathBuf ;
15- use std:: sync:: LazyLock ;
1614use tempfile:: NamedTempFile ;
1715use tokio:: fs;
1816use tokio:: process:: Command ;
1917
20- static TEMPLATE_ENV : LazyLock < Environment < ' _ > > = LazyLock :: new ( || {
21- let mut env = Environment :: new ( ) ;
22-
23- // Add custom filter for escaping Typst special characters
24- env. add_filter ( "typst_escape" , |value : String | -> String {
25- value
26- . replace ( '\\' , "\\ \\ " ) // Escape backslashes first
27- . replace ( '"' , "\\ \" " ) // Escape double quotes
28- // Note: No need to escape # characters when inside double-quoted strings
29- } ) ;
30-
31- // Add custom filter for formatting byte sizes
32- env. add_filter ( "format_bytes" , format_bytes) ;
33-
34- // Add custom filter for formatting numbers with k/M suffixes
35- env. add_filter ( "format_number" , format_number) ;
36-
37- let template_str = include_str ! ( "../templates/og-image.typ.j2" ) ;
38- env. add_template ( "og-image.typ" , template_str) . unwrap ( ) ;
39- env
40- } ) ;
41-
4218/// Data structure containing information needed to generate an OpenGraph image
4319/// for a crates.io crate.
4420#[ derive( Debug , Clone , Serialize ) ]
@@ -56,10 +32,13 @@ pub struct OgImageData<'a> {
5632 /// Author information
5733 pub authors : & ' a [ OgImageAuthorData < ' a > ] ,
5834 /// Source lines of code count (optional)
35+ #[ serde( serialize_with = "serialize_optional_number" ) ]
5936 pub lines_of_code : Option < u32 > ,
6037 /// Package size in bytes
38+ #[ serde( serialize_with = "serialize_bytes" ) ]
6139 pub crate_size : u32 ,
6240 /// Total number of releases
41+ #[ serde( serialize_with = "serialize_number" ) ]
6342 pub releases : u32 ,
6443}
6544
@@ -187,20 +166,6 @@ impl OgImageGenerator {
187166 Ok ( avatar_map)
188167 }
189168
190- /// Generates the Typst template content from the provided data.
191- ///
192- /// This private method renders the Jinja2 template with the provided data
193- /// and returns the resulting Typst markup as a string.
194- fn generate_template (
195- & self ,
196- data : & OgImageData < ' _ > ,
197- avatar_map : & HashMap < & str , String > ,
198- ) -> Result < String , OgImageError > {
199- let template = TEMPLATE_ENV . get_template ( "og-image.typ" ) ?;
200- let rendered = template. render ( context ! { data, avatar_map } ) ?;
201- Ok ( rendered)
202- }
203-
204169 /// Generates an OpenGraph image using the provided data.
205170 ///
206171 /// This method creates a temporary directory with all the necessary files
@@ -258,19 +223,30 @@ impl OgImageGenerator {
258223 // Process avatars - download URLs and copy assets
259224 let avatar_map = self . process_avatars ( & data, & assets_dir) . await ?;
260225
261- // Create og-image.typ file using minijinja template
262- let rendered = self . generate_template ( & data , & avatar_map ) ? ;
226+ // Copy the static Typst template file
227+ let template_content = include_str ! ( "../templates/og-image.typ" ) ;
263228 let typ_file_path = temp_dir. path ( ) . join ( "og-image.typ" ) ;
264- fs:: write ( & typ_file_path, rendered ) . await ?;
229+ fs:: write ( & typ_file_path, template_content ) . await ?;
265230
266231 // Create a named temp file for the output PNG
267232 let output_file = NamedTempFile :: new ( ) . map_err ( OgImageError :: TempFileError ) ?;
268233
269- // Run typst compile command
234+ // Serialize data and avatar_map to JSON
235+ let json_data = serde_json:: to_string ( & data) ;
236+ let json_data = json_data. map_err ( OgImageError :: JsonSerializationError ) ?;
237+
238+ let json_avatar_map = serde_json:: to_string ( & avatar_map) ;
239+ let json_avatar_map = json_avatar_map. map_err ( OgImageError :: JsonSerializationError ) ?;
240+
241+ // Run typst compile command with input data
270242 let output = Command :: new ( & self . typst_binary_path )
271243 . arg ( "compile" )
272244 . arg ( "--format" )
273245 . arg ( "png" )
246+ . arg ( "--input" )
247+ . arg ( format ! ( "data={}" , json_data) )
248+ . arg ( "--input" )
249+ . arg ( format ! ( "avatar_map={}" , json_avatar_map) )
274250 . arg ( & typ_file_path)
275251 . arg ( output_file. path ( ) )
276252 . output ( )
@@ -313,22 +289,6 @@ mod tests {
313289 OgImageAuthorData :: new ( name, Some ( "test-avatar" ) )
314290 }
315291
316- fn create_standard_test_data ( ) -> OgImageData < ' static > {
317- static AUTHORS : & [ OgImageAuthorData < ' _ > ] = & [ author_with_avatar ( "alice" ) , author ( "bob" ) ] ;
318-
319- OgImageData {
320- name : "example-crate" ,
321- version : "v2.1.0" ,
322- description : "A comprehensive example crate showcasing various OpenGraph features" ,
323- license : "MIT OR Apache-2.0" ,
324- tags : & [ "web" , "api" , "async" , "json" , "http" ] ,
325- authors : AUTHORS ,
326- lines_of_code : Some ( 5500 ) ,
327- crate_size : 128000 ,
328- releases : 15 ,
329- }
330- }
331-
332292 fn create_minimal_test_data ( ) -> OgImageData < ' static > {
333293 static AUTHORS : & [ OgImageAuthorData < ' _ > ] = & [ author ( "author" ) ] ;
334294
@@ -428,12 +388,6 @@ mod tests {
428388 . is_err ( )
429389 }
430390
431- fn generate_template ( data : OgImageData < ' _ > , avatar_map : HashMap < & str , String > ) -> String {
432- OgImageGenerator :: default ( )
433- . generate_template ( & data, & avatar_map)
434- . expect ( "Failed to generate template" )
435- }
436-
437391 async fn generate_image ( data : OgImageData < ' _ > ) -> Option < Vec < u8 > > {
438392 if skip_if_typst_unavailable ( ) {
439393 return None ;
@@ -449,33 +403,6 @@ mod tests {
449403 Some ( std:: fs:: read ( temp_file. path ( ) ) . expect ( "Failed to read generated image" ) )
450404 }
451405
452- #[ test]
453- fn test_generate_template_snapshot ( ) {
454- let data = create_standard_test_data ( ) ;
455- let avatar_map = HashMap :: from ( [ ( "test-avatar" , "avatar_0.png" . to_string ( ) ) ] ) ;
456-
457- let template_content = generate_template ( data, avatar_map) ;
458- insta:: assert_snapshot!( "generated_template.typ" , template_content) ;
459- }
460-
461- #[ test]
462- fn test_generate_template_minimal_snapshot ( ) {
463- let data = create_minimal_test_data ( ) ;
464- let avatar_map = HashMap :: new ( ) ;
465-
466- let template_content = generate_template ( data, avatar_map) ;
467- insta:: assert_snapshot!( "generated_template_minimal.typ" , template_content) ;
468- }
469-
470- #[ test]
471- fn test_generate_template_escaping_snapshot ( ) {
472- let data = create_escaping_test_data ( ) ;
473- let avatar_map = HashMap :: from ( [ ( "test-avatar" , "avatar_0.png" . to_string ( ) ) ] ) ;
474-
475- let template_content = generate_template ( data, avatar_map) ;
476- insta:: assert_snapshot!( "generated_template_escaping.typ" , template_content) ;
477- }
478-
479406 #[ tokio:: test]
480407 async fn test_generate_og_image_snapshot ( ) {
481408 let data = create_simple_test_data ( ) ;
0 commit comments