@@ -5,40 +5,17 @@ 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 ;
12+ use serde_json;
1313use std:: collections:: HashMap ;
1414use std:: path:: PathBuf ;
15- use std:: sync:: LazyLock ;
1615use tempfile:: NamedTempFile ;
1716use tokio:: fs;
1817use tokio:: process:: Command ;
1918
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-
4219/// Data structure containing information needed to generate an OpenGraph image
4320/// for a crates.io crate.
4421#[ derive( Debug , Clone , Serialize ) ]
@@ -56,10 +33,13 @@ pub struct OgImageData<'a> {
5633 /// Author information
5734 pub authors : & ' a [ OgImageAuthorData < ' a > ] ,
5835 /// Source lines of code count (optional)
36+ #[ serde( serialize_with = "serialize_optional_number" ) ]
5937 pub lines_of_code : Option < u32 > ,
6038 /// Package size in bytes
39+ #[ serde( serialize_with = "serialize_bytes" ) ]
6140 pub crate_size : u32 ,
6241 /// Total number of releases
42+ #[ serde( serialize_with = "serialize_number" ) ]
6343 pub releases : u32 ,
6444}
6545
@@ -187,20 +167,6 @@ impl OgImageGenerator {
187167 Ok ( avatar_map)
188168 }
189169
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-
204170 /// Generates an OpenGraph image using the provided data.
205171 ///
206172 /// This method creates a temporary directory with all the necessary files
@@ -258,19 +224,30 @@ impl OgImageGenerator {
258224 // Process avatars - download URLs and copy assets
259225 let avatar_map = self . process_avatars ( & data, & assets_dir) . await ?;
260226
261- // Create og-image.typ file using minijinja template
262- let rendered = self . generate_template ( & data , & avatar_map ) ? ;
227+ // Copy the static Typst template file
228+ let template_content = include_str ! ( "../templates/og-image.typ" ) ;
263229 let typ_file_path = temp_dir. path ( ) . join ( "og-image.typ" ) ;
264- fs:: write ( & typ_file_path, rendered ) . await ?;
230+ fs:: write ( & typ_file_path, template_content ) . await ?;
265231
266232 // Create a named temp file for the output PNG
267233 let output_file = NamedTempFile :: new ( ) . map_err ( OgImageError :: TempFileError ) ?;
268234
269- // Run typst compile command
235+ // Serialize data and avatar_map to JSON
236+ let json_data = serde_json:: to_string ( & data) ;
237+ let json_data = json_data. map_err ( OgImageError :: JsonSerializationError ) ?;
238+
239+ let json_avatar_map = serde_json:: to_string ( & avatar_map) ;
240+ let json_avatar_map = json_avatar_map. map_err ( OgImageError :: JsonSerializationError ) ?;
241+
242+ // Run typst compile command with input data
270243 let output = Command :: new ( & self . typst_binary_path )
271244 . arg ( "compile" )
272245 . arg ( "--format" )
273246 . arg ( "png" )
247+ . arg ( "--input" )
248+ . arg ( format ! ( "data={}" , json_data) )
249+ . arg ( "--input" )
250+ . arg ( format ! ( "avatar_map={}" , json_avatar_map) )
274251 . arg ( & typ_file_path)
275252 . arg ( output_file. path ( ) )
276253 . output ( )
@@ -313,22 +290,6 @@ mod tests {
313290 OgImageAuthorData :: new ( name, Some ( "test-avatar" ) )
314291 }
315292
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-
332293 fn create_minimal_test_data ( ) -> OgImageData < ' static > {
333294 static AUTHORS : & [ OgImageAuthorData < ' _ > ] = & [ author ( "author" ) ] ;
334295
@@ -428,12 +389,6 @@ mod tests {
428389 . is_err ( )
429390 }
430391
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-
437392 async fn generate_image ( data : OgImageData < ' _ > ) -> Option < Vec < u8 > > {
438393 if skip_if_typst_unavailable ( ) {
439394 return None ;
@@ -449,33 +404,6 @@ mod tests {
449404 Some ( std:: fs:: read ( temp_file. path ( ) ) . expect ( "Failed to read generated image" ) )
450405 }
451406
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-
479407 #[ tokio:: test]
480408 async fn test_generate_og_image_snapshot ( ) {
481409 let data = create_simple_test_data ( ) ;
0 commit comments