gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 701HTML 654Markdown 12TOML 62332 Definitions

⎇ ebd376fbebd0ba73c79aa00d1835918680ef82a0 scripts/gentest/src/main.rs Rust · 933 lines · 36.1 KB History Who touched what raw

Grid: Add support for repeat() with an integer repetition count (#342) ebd376fbe · Nico Burns ·

1use std::fs;
2use std::path::{Path, PathBuf};
3use std::process::Command;
4
5use convert_case::{Case, Casing};
6use fantoccini::{Client, ClientBuilder};
7use log::*;
8use proc_macro2::{Span, TokenStream};
9use quote::{format_ident, quote};
10use serde_json::Value;
11use syn::Ident;
12
13#[tokio::main]
14async fn main() {
15    env_logger::init();
16    // this requires being run by cargo, which is iffy
17    let root_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
18    let repo_root = root_dir.parent().and_then(Path::parent).unwrap();
19
20    let fixtures_root = repo_root.join("test_fixtures");
21    let fixtures = fs::read_dir(fixtures_root).unwrap();
22
23    info!("reading test fixtures from disk");
24    let mut fixtures: Vec<_> = fixtures
25        .into_iter()
26        .filter_map(|a| a.ok())
27        .filter(|f| !f.file_name().to_string_lossy().starts_with('x')) // ignore tests beginning with x
28        .filter(|f| f.path().is_file() && f.path().extension().map(|p| p == "html").unwrap_or(false))
29        .map(|f| {
30            let fixture_path = f.path();
31            let name = fixture_path.file_stem().unwrap().to_str().unwrap().to_string();
32            (name, fixture_path)
33        })
34        .collect();
35    fixtures.sort_unstable_by_key(|f| f.0.clone());
36
37    info!("starting webdriver instance");
38    let webdriver_url = "http://localhost:4444";
39    let mut webdriver_handle = Command::new("chromedriver")
40        .arg("--port=4444")
41        .spawn()
42        .expect("ChromeDriver not found: Make sure you have it installed and added to your PATH.");
43
44    // this is silly, but it works
45    std::thread::sleep(std::time::Duration::from_secs(1));
46
47    let mut caps = serde_json::map::Map::new();
48    let chrome_opts = serde_json::json!({ "args": ["--headless", "--disable-gpu"] });
49    caps.insert("goog:chromeOptions".to_string(), chrome_opts.clone());
50
51    info!("spawning webdriver client and collecting test descriptions");
52    let client = ClientBuilder::native().capabilities(caps.clone()).connect(webdriver_url).await.unwrap();
53
54    let mut test_descs = vec![];
55    for (name, fixture_path) in fixtures {
56        test_descs.push(test_root_element(client.clone(), name, fixture_path).await);
57    }
58
59    info!("killing webdriver instance...");
60    webdriver_handle.kill().unwrap();
61
62    info!("generating test sources and concatenating...");
63
64    let bench_descs: Vec<_> = test_descs
65        .iter()
66        .map(|(name, description)| {
67            debug!("generating bench contents for {}", &name);
68            (name.clone(), generate_bench(description))
69        })
70        .collect();
71
72    let test_descs: Vec<_> = test_descs
73        .iter()
74        .map(|(name, description)| {
75            debug!("generating test contents for {}", &name);
76            (name.clone(), generate_test(name, description))
77        })
78        .collect();
79
80    let benchmarks: Vec<_> = test_descs
81        .iter()
82        .map(|(name, _)| {
83            let bench_mod = Ident::new(name, Span::call_site());
84            if name.starts_with("grid") {
85                quote!(#[cfg(feature = "grid")] #bench_mod::compute())
86            } else {
87                quote!(#bench_mod::compute())
88            }
89        })
90        .collect();
91
92    let mod_statemnts = test_descs
93        .iter()
94        .map(|(name, _)| {
95            let name_ident = Ident::new(name, Span::call_site());
96            if name.starts_with("grid") {
97                quote!(#[cfg(feature = "grid")] mod #name_ident;)
98            } else {
99                quote!(mod #name_ident;)
100            }
101        })
102        .fold(quote!(), |a, b| quote!(#a #b));
103    let generic_measure_function = generate_generic_measure_function();
104
105    let test_mod_file = quote!(
106        #generic_measure_function
107        #mod_statemnts
108    );
109
110    let bench_mod_file = quote!(
111        use criterion::{criterion_group, criterion_main, Criterion};
112
113        #generic_measure_function
114
115        #mod_statemnts
116
117        fn benchmark(c: &mut Criterion) {
118            c.bench_function("generated benchmarks", |b| {
119                b.iter(|| { #(#benchmarks;)* })
120            });
121        }
122
123        criterion_group!(benches, benchmark);
124        criterion_main!(benches);
125    );
126
127    info!("writing generated benchmarks file to disk...");
128    let benches_base_path = repo_root.join("benches").join("generated");
129    fs::remove_dir_all(&benches_base_path).unwrap();
130    fs::create_dir(&benches_base_path).unwrap();
131    for (name, bench_body) in bench_descs {
132        let mut bench_filename = benches_base_path.join(&name);
133        bench_filename.set_extension("rs");
134        debug!("writing {} to disk...", &name);
135        fs::write(bench_filename, bench_body.to_string()).unwrap();
136    }
137    fs::write(benches_base_path.join("mod.rs"), bench_mod_file.to_string()).unwrap();
138
139    info!("writing generated test file to disk...");
140    let tests_base_path = repo_root.join("tests").join("generated");
141    fs::remove_dir_all(&tests_base_path).unwrap();
142    fs::create_dir(&tests_base_path).unwrap();
143    for (name, test_body) in test_descs {
144        let mut test_filename = tests_base_path.join(&name);
145        test_filename.set_extension("rs");
146        debug!("writing {} to disk...", &name);
147        fs::write(test_filename, test_body.to_string()).unwrap();
148    }
149    fs::write(tests_base_path.join("mod.rs"), test_mod_file.to_string()).unwrap();
150
151    info!("formatting the source directory");
152    Command::new("cargo").arg("fmt").current_dir(repo_root).status().unwrap();
153}
154
155async fn test_root_element(client: Client, name: String, fixture_path: impl AsRef<Path>) -> (String, Value) {
156    let fixture_path = fixture_path.as_ref();
157
158    let url = format!("file://{}", fixture_path.display());
159
160    client.goto(&url).await.unwrap();
161    let description = client
162        .execute("return JSON.stringify(describeElement(document.getElementById('test-root')))", vec![])
163        .await
164        .unwrap();
165    let description_string = description.as_str().unwrap();
166    let description = serde_json::from_str(description_string).unwrap();
167    (name, description)
168}
169
170fn generate_bench(description: &Value) -> TokenStream {
171    let node_description = generate_node("node", description);
172
173    quote!(
174        pub fn compute() {
175            #[allow(unused_imports)]
176            use taffy::prelude::*;
177            let mut taffy = taffy::Taffy::new();
178            #node_description
179            taffy.compute_layout(node, taffy::geometry::Size::MAX_CONTENT).unwrap();
180        }
181    )
182}
183
184fn generate_test(name: impl AsRef<str>, description: &Value) -> TokenStream {
185    let name = name.as_ref();
186    let name = Ident::new(name, Span::call_site());
187    let node_description = generate_node("node", description);
188    let assertions = generate_assertions("node", description);
189
190    quote!(
191        #[test]
192        fn #name() {
193            #[allow(unused_imports)]
194            use taffy::{layout::Layout, prelude::*};
195            use slotmap::Key;
196            let mut taffy = taffy::Taffy::new();
197            #node_description
198            taffy.compute_layout(node, taffy::geometry::Size::MAX_CONTENT).unwrap();
199
200            println!("\nComputed tree:");
201            taffy::debug::print_tree(&taffy, node);
202            println!();
203
204            #assertions
205        }
206    )
207}
208
209fn generate_assertions(ident: &str, node: &Value) -> TokenStream {
210    let layout = &node["layout"];
211
212    let read_f32 = |s: &str| layout[s].as_f64().unwrap() as f32;
213    let width = read_f32("width");
214    let height = read_f32("height");
215    let x = read_f32("x");
216    let y = read_f32("y");
217
218    let children = {
219        let mut c = Vec::new();
220        if let Value::Array(ref value) = node["children"] {
221            for (idx, child) in value.iter().enumerate() {
222                c.push(generate_assertions(&format!("{ident}{idx}"), child));
223            }
224        };
225        c.into_iter().fold(quote!(), |a, b| quote!(#a #b))
226    };
227
228    let ident = Ident::new(ident, Span::call_site());
229
230    quote!(
231        let Layout { size, location, .. } = taffy.layout(#ident).unwrap();
232        assert_eq!(size.width, #width, "width of node {:?}. Expected {}. Actual {}", #ident.data(),  #width, size.width);
233        assert_eq!(size.height, #height, "height of node {:?}. Expected {}. Actual {}", #ident.data(),  #height, size.height);
234        assert_eq!(location.x, #x, "x of node {:?}. Expected {}. Actual {}", #ident.data(),  #x, location.x);
235        assert_eq!(location.y, #y, "y of node {:?}. Expected {}. Actual {}", #ident.data(),  #y, location.y);
236
237        #children
238    )
239}
240
241macro_rules! dim_quoted_renamed {
242    ($obj:ident, $in_name:ident, $out_name:ident, $value_mapper:ident, $default:expr) => {
243        let $out_name = match $obj.get(stringify!($in_name)) {
244            Some(Value::Object(ref value)) => {
245                let dim = $value_mapper(value);
246                quote!($out_name: #dim,)
247            }
248            _ => {
249                let dim = $default;
250                quote!($out_name: #dim,)
251            }
252        };
253    };
254}
255
256macro_rules! dim_quoted {
257    ($obj:ident, $dim_name:ident, $value_mapper: ident, $default:expr) => {
258        dim_quoted_renamed!($obj, $dim_name, $dim_name, $value_mapper, $default)
259    };
260}
261
262macro_rules! edges_quoted {
263    ($style:ident, $val:ident, $value_mapper:ident, $default_value: expr) => {
264        let $val = match $style[stringify!($val)] {
265            Value::Object(ref value) => {
266                dim_quoted!(value, left, $value_mapper, $default_value);
267                dim_quoted!(value, right, $value_mapper, $default_value);
268                dim_quoted!(value, top, $value_mapper, $default_value);
269                dim_quoted!(value, bottom, $value_mapper, $default_value);
270
271                let edges = quote!(taffy::geometry::Rect {
272                    #left #right #top #bottom
273                });
274
275                quote!($val: #edges,)
276            },
277            _ => quote!(),
278        };
279    };
280}
281
282fn generate_node(ident: &str, node: &Value) -> TokenStream {
283    let style = &node["style"];
284
285    fn snake_case_ident(ident_name: &str) -> Ident {
286        let name_snake_case = ident_name.to_case(Case::Snake);
287        format_ident!("{}", name_snake_case)
288    }
289
290    fn quote_object_value(
291        prop_name: &str,
292        style: &Value,
293        quoter: impl Fn(&serde_json::Map<String, Value>) -> TokenStream,
294    ) -> Option<TokenStream> {
295        match style[prop_name.to_case(Case::Camel)] {
296            Value::Object(ref value) => Some(quoter(value)),
297            _ => None,
298        }
299    }
300
301    fn quote_prop(prop_name: &str, value: TokenStream) -> TokenStream {
302        let ident = snake_case_ident(prop_name);
303        quote!(#ident: #value,)
304    }
305
306    fn quote_object_prop(
307        prop_name: &str,
308        style: &Value,
309        quoter: impl Fn(&serde_json::Map<String, Value>) -> TokenStream,
310    ) -> TokenStream {
311        match quote_object_value(prop_name, style, quoter) {
312            Some(prop_value) => quote_prop(prop_name, prop_value),
313            None => quote!(),
314        }
315    }
316
317    fn quote_array_prop(prop_name: &str, style: &Value, quoter: impl Fn(&[Value]) -> TokenStream) -> TokenStream {
318        let prop_name_snake_case = prop_name.to_case(Case::Snake);
319        let prop_name_camel_case = prop_name.to_case(Case::Camel);
320        let prop_name_ident = format_ident!("{}", prop_name_snake_case);
321        match style[prop_name_camel_case] {
322            Value::Array(ref value) => {
323                let prop_value = quoter(value);
324                quote!(#prop_name_ident: #prop_value,)
325            }
326            _ => quote!(),
327        }
328    }
329
330    fn get_string_value<'a, 'b, 'c: 'b>(prop_name: &'a str, style: &'c Value) -> Option<&'b str> {
331        match style[prop_name.to_case(Case::Camel)] {
332            Value::String(ref value) => Some(value),
333            _ => None,
334        }
335    }
336
337    fn get_number_value<'a, 'b, 'c: 'b>(prop_name: &'a str, style: &'c Value) -> Option<f32> {
338        match style[prop_name.to_case(Case::Camel)] {
339            Value::Number(ref value) => Some(value.as_f64().unwrap() as f32),
340            _ => None,
341        }
342    }
343
344    fn quote_number_prop(prop_name: &str, style: &Value, quoter: impl Fn(f32) -> TokenStream) -> TokenStream {
345        let prop_name_snake_case = prop_name.to_case(Case::Snake);
346        let prop_name_camel_case = prop_name.to_case(Case::Camel);
347        let prop_name_ident = format_ident!("{}", prop_name_snake_case);
348        match style[prop_name_camel_case] {
349            Value::Number(ref value) => {
350                let prop_value = quoter(value.as_f64().unwrap() as f32);
351                quote!(#prop_name_ident: #prop_value,)
352            }
353            _ => quote!(),
354        }
355    }
356
357    let display = match style["display"] {
358        Value::String(ref value) => match value.as_ref() {
359            "none" => quote!(display: taffy::style::Display::None,),
360            "grid" => quote!(display: taffy::style::Display::Grid,),
361            _ => quote!(display: taffy::style::Display::Flex,),
362        },
363        _ => quote!(),
364    };
365
366    let position = match style["position"] {
367        Value::String(ref value) => match value.as_ref() {
368            "absolute" => quote!(position: taffy::style::Position::Absolute,),
369            _ => quote!(),
370        },
371        _ => quote!(),
372    };
373
374    let direction = match style["direction"] {
375        Value::String(ref value) => match value.as_ref() {
376            "rtl" => quote!(direction: taffy::style::Direction::RTL,),
377            "ltr" => quote!(direction: taffy::style::Direction::LTR,),
378            _ => quote!(),
379        },
380        _ => quote!(),
381    };
382
383    let flex_direction = match style["flexDirection"] {
384        Value::String(ref value) => match value.as_ref() {
385            "row-reverse" => quote!(flex_direction: taffy::style::FlexDirection::RowReverse,),
386            "column" => quote!(flex_direction: taffy::style::FlexDirection::Column,),
387            "column-reverse" => quote!(flex_direction: taffy::style::FlexDirection::ColumnReverse,),
388            _ => quote!(),
389        },
390        _ => quote!(),
391    };
392
393    let flex_wrap = match style["flexWrap"] {
394        Value::String(ref value) => match value.as_ref() {
395            "wrap" => quote!(flex_wrap: taffy::style::FlexWrap::Wrap,),
396            "wrap-reverse" => quote!(flex_wrap: taffy::style::FlexWrap::WrapReverse,),
397            _ => quote!(),
398        },
399        _ => quote!(),
400    };
401
402    let overflow = match style["overflow"] {
403        Value::String(ref value) => match value.as_ref() {
404            "hidden" => quote!(overflow: taffy::style::Overflow::Hidden,),
405            "scroll" => quote!(overflow: taffy::style::Overflow::Scroll,),
406            _ => quote!(),
407        },
408        _ => quote!(),
409    };
410
411    let align_items = match style["alignItems"] {
412        Value::String(ref value) => match value.as_ref() {
413            "start" => quote!(align_items: Some(taffy::style::AlignItems::Start),),
414            "end" => quote!(align_items: Some(taffy::style::AlignItems::End),),
415            "flex-start" => quote!(align_items: Some(taffy::style::AlignItems::FlexStart),),
416            "flex-end" => quote!(align_items: Some(taffy::style::AlignItems::FlexEnd),),
417            "center" => quote!(align_items: Some(taffy::style::AlignItems::Center),),
418            "baseline" => quote!(align_items: Some(taffy::style::AlignItems::Baseline),),
419            "stretch" => quote!(align_items: Some(taffy::style::AlignItems::Stretch),),
420            _ => quote!(),
421        },
422        _ => quote!(),
423    };
424
425    let align_self = match style["alignSelf"] {
426        Value::String(ref value) => match value.as_ref() {
427            "start" => quote!(align_self: Some(taffy::style::AlignSelf::Start),),
428            "end" => quote!(align_self: Some(taffy::style::AlignSelf::End),),
429            "flex-start" => quote!(align_self: Some(taffy::style::AlignSelf::FlexStart),),
430            "flex-end" => quote!(align_self: Some(taffy::style::AlignSelf::FlexEnd),),
431            "center" => quote!(align_self: Some(taffy::style::AlignSelf::Center),),
432            "baseline" => quote!(align_self: Some(taffy::style::AlignSelf::Baseline),),
433            "stretch" => quote!(align_self: Some(taffy::style::AlignSelf::Stretch),),
434            _ => quote!(),
435        },
436        _ => quote!(),
437    };
438
439    let justify_items = match style["justifyItems"] {
440        Value::String(ref value) => match value.as_ref() {
441            "start" => quote!(justify_items: Some(taffy::style::JustifyItems::Start),),
442            "end" => quote!(justify_items: Some(taffy::style::JustifyItems::End),),
443            "flex-start" => quote!(justify_items: Some(taffy::style::JustifyItems::FlexStart),),
444            "flex-end" => quote!(justify_items: Some(taffy::style::JustifyItems::FlexEnd),),
445            "center" => quote!(justify_items: Some(taffy::style::JustifyItems::Center),),
446            "baseline" => quote!(justify_items: Some(taffy::style::JustifyItems::Baseline),),
447            "stretch" => quote!(justify_items: Some(taffy::style::JustifyItems::Stretch),),
448            _ => quote!(),
449        },
450        _ => quote!(),
451    };
452
453    let justify_self = match style["justifySelf"] {
454        Value::String(ref value) => match value.as_ref() {
455            "start" => quote!(justify_self: Some(taffy::style::JustifySelf::Start),),
456            "end" => quote!(justify_self: Some(taffy::style::JustifySelf::End),),
457            "flex-start" => quote!(justify_self: Some(taffy::style::JustifySelf::FlexStart),),
458            "flex-end" => quote!(justify_self: Some(taffy::style::JustifySelf::FlexEnd),),
459            "center" => quote!(justify_self: Some(taffy::style::JustifySelf::Center),),
460            "baseline" => quote!(justify_self: Some(taffy::style::JustifySelf::Baseline),),
461            "stretch" => quote!(justify_self: Some(taffy::style::JustifySelf::Stretch),),
462            _ => quote!(),
463        },
464        _ => quote!(),
465    };
466
467    let align_content = match style["alignContent"] {
468        Value::String(ref value) => match value.as_ref() {
469            "start" => quote!(align_content: Some(taffy::style::AlignContent::Start),),
470            "end" => quote!(align_content: Some(taffy::style::AlignContent::End),),
471            "flex-start" => quote!(align_content: Some(taffy::style::AlignContent::FlexStart),),
472            "flex-end" => quote!(align_content: Some(taffy::style::AlignContent::FlexEnd),),
473            "center" => quote!(align_content: Some(taffy::style::AlignContent::Center),),
474            "stretch" => quote!(align_content: Some(taffy::style::AlignContent::Stretch),),
475            "space-between" => quote!(align_content: Some(taffy::style::AlignContent::SpaceBetween),),
476            "space-around" => quote!(align_content: Some(taffy::style::AlignContent::SpaceAround),),
477            "space-evenly" => quote!(align_content: Some(taffy::style::AlignContent::SpaceEvenly),),
478            _ => quote!(),
479        },
480        _ => quote!(),
481    };
482
483    let justify_content = match style["justifyContent"] {
484        Value::String(ref value) => match value.as_ref() {
485            "start" => quote!(justify_content: Some(taffy::style::JustifyContent::Start),),
486            "end" => quote!(justify_content: Some(taffy::style::JustifyContent::End),),
487            "flex-start" => quote!(justify_content: Some(taffy::style::JustifyContent::FlexStart),),
488            "flex-end" => quote!(justify_content: Some(taffy::style::JustifyContent::FlexEnd),),
489            "center" => quote!(justify_content: Some(taffy::style::JustifyContent::Center),),
490            "stretch" => quote!(justify_content: Some(taffy::style::AlignContent::Stretch),),
491            "space-between" => quote!(justify_content: Some(taffy::style::JustifyContent::SpaceBetween),),
492            "space-around" => quote!(justify_content: Some(taffy::style::JustifyContent::SpaceAround),),
493            "space-evenly" => quote!(justify_content: Some(taffy::style::JustifyContent::SpaceEvenly),),
494            _ => quote!(),
495        },
496        _ => quote!(),
497    };
498
499    let flex_grow = quote_number_prop("flex_grow", style, |value: f32| quote!(#value));
500    let flex_shrink = quote_number_prop("flex_shrink", style, |value: f32| quote!(#value));
501
502    let flex_basis = quote_object_prop("flex_basis", style, generate_dimension);
503    let size = quote_object_prop("size", style, generate_size);
504    let min_size = quote_object_prop("min_size", style, generate_size);
505    let max_size = quote_object_prop("max_size", style, generate_size);
506    let aspect_ratio = quote_number_prop("aspect_ratio", style, |value: f32| quote!(Some(#value)));
507
508    let gap = quote_object_prop("gap", style, generate_gap);
509
510    let grid_template_rows = quote_array_prop("grid_template_rows", style, generate_track_definition_list);
511    let grid_template_columns = quote_array_prop("grid_template_columns", style, generate_track_definition_list);
512    let grid_auto_rows = quote_array_prop("grid_auto_rows", style, generate_track_definition_list);
513    let grid_auto_columns = quote_array_prop("grid_auto_columns", style, generate_track_definition_list);
514    let grid_auto_flow = quote_object_prop("grid_auto_flow", style, generate_grid_auto_flow);
515
516    let default_grid_placement = quote!(taffy::style::GridPlacement::Auto);
517
518    let grid_row_start = quote_object_value("grid_row_start", style, generate_grid_position);
519    let grid_row_end = quote_object_value("grid_row_end", style, generate_grid_position);
520    let grid_row = if grid_row_start.is_some() || grid_row_end.is_some() {
521        quote_prop(
522            "grid_row",
523            generate_line(
524                grid_row_start.unwrap_or(default_grid_placement.clone()),
525                grid_row_end.unwrap_or(default_grid_placement.clone()),
526            ),
527        )
528    } else {
529        quote!()
530    };
531
532    let grid_column_start = quote_object_value("grid_column_start", style, generate_grid_position);
533    let grid_column_end = quote_object_value("grid_column_end", style, generate_grid_position);
534    let grid_column = if grid_column_start.is_some() || grid_column_end.is_some() {
535        quote_prop(
536            "grid_column",
537            generate_line(
538                grid_column_start.unwrap_or(default_grid_placement.clone()),
539                grid_column_end.unwrap_or(default_grid_placement),
540            ),
541        )
542    } else {
543        quote!()
544    };
545
546    let text_content = get_string_value("text_content", node);
547    let writing_mode = get_string_value("writingMode", style);
548    let raw_aspect_ratio = get_number_value("aspect_ratio", style);
549    let measure_func: Option<_> =
550        text_content.map(|text| generate_measure_function(text, writing_mode, raw_aspect_ratio));
551
552    edges_quoted!(style, margin, generate_length_percentage_auto, quote!(zero()));
553    edges_quoted!(style, padding, generate_length_percentage, quote!(zero()));
554    edges_quoted!(style, border, generate_length_percentage, quote!(zero()));
555    edges_quoted!(style, inset, generate_length_percentage_auto, quote!(auto()));
556
557    // Quote children
558    let child_descriptions: Vec<Value> = match node["children"] {
559        Value::Array(ref value) => value.clone(),
560        _ => vec![],
561    };
562    let has_children = !child_descriptions.is_empty();
563    let (children_body, children) = if has_children {
564        let body = child_descriptions
565            .iter()
566            .enumerate()
567            .map(|(i, child)| generate_node(&format!("{ident}{i}"), child))
568            .collect();
569        let idents = child_descriptions
570            .iter()
571            .enumerate()
572            .map(|(i, _)| Ident::new(&format!("{ident}{i}"), Span::call_site()))
573            .collect::<Vec<_>>();
574        (body, quote!(&[#(#idents),*]))
575    } else {
576        (quote!(), quote!())
577    };
578
579    let ident = Ident::new(ident, Span::call_site());
580
581    let style = quote!(taffy::style::Style {
582        #display
583        #direction
584        #position
585        #flex_direction
586        #flex_wrap
587        #overflow
588        #align_items
589        #align_self
590        #justify_items
591        #justify_self
592        #align_content
593        #justify_content
594        #flex_grow
595        #flex_shrink
596        #flex_basis
597        #gap
598        #grid_template_rows
599        #grid_template_columns
600        #grid_auto_rows
601        #grid_auto_columns
602        #grid_auto_flow
603        #grid_row
604        #grid_column
605        #size
606        #min_size
607        #max_size
608        #aspect_ratio
609        #margin
610        #padding
611        #inset
612        #border
613        ..Default::default()
614    });
615
616    if has_children {
617        quote!(
618            #children_body
619            let #ident = taffy.new_with_children(#style,#children).unwrap();
620        )
621    } else if measure_func.is_some() {
622        quote!(let #ident = taffy.new_leaf_with_measure(#style,#measure_func,).unwrap();)
623    } else {
624        quote!(let #ident = taffy.new_leaf(#style).unwrap();)
625    }
626}
627
628fn generate_size(size: &serde_json::Map<String, Value>) -> TokenStream {
629    dim_quoted!(size, width, generate_dimension, quote!(auto()));
630    dim_quoted!(size, height, generate_dimension, quote!(auto()));
631    quote!(
632        taffy::geometry::Size {
633            #width #height
634        }
635    )
636}
637
638fn generate_gap(size: &serde_json::Map<String, Value>) -> TokenStream {
639    dim_quoted_renamed!(size, column, width, generate_length_percentage, quote!(zero()));
640    dim_quoted_renamed!(size, row, height, generate_length_percentage, quote!(zero()));
641    quote!(
642        taffy::geometry::Size {
643            #width #height
644        }
645    )
646}
647
648fn generate_length_percentage(dimen: &serde_json::Map<String, Value>) -> TokenStream {
649    let unit = dimen.get("unit").unwrap();
650    let value = || dimen.get("value").unwrap().as_f64().unwrap() as f32;
651
652    match unit {
653        Value::String(ref unit) => match unit.as_ref() {
654            "points" => {
655                let value = value();
656                quote!(taffy::style::LengthPercentage::Points(#value))
657            }
658            "percent" => {
659                let value = value();
660                quote!(taffy::style::LengthPercentage::Percent(#value))
661            }
662            _ => unreachable!(),
663        },
664        _ => unreachable!(),
665    }
666}
667
668fn generate_length_percentage_auto(dimen: &serde_json::Map<String, Value>) -> TokenStream {
669    let unit = dimen.get("unit").unwrap();
670    let value = || dimen.get("value").unwrap().as_f64().unwrap() as f32;
671
672    match unit {
673        Value::String(ref unit) => match unit.as_ref() {
674            "auto" => quote!(taffy::style::LengthPercentageAuto::Auto),
675            "points" => {
676                let value = value();
677                quote!(taffy::style::LengthPercentageAuto::Points(#value))
678            }
679            "percent" => {
680                let value = value();
681                quote!(taffy::style::LengthPercentageAuto::Percent(#value))
682            }
683            _ => unreachable!(),
684        },
685        _ => unreachable!(),
686    }
687}
688
689fn generate_dimension(dimen: &serde_json::Map<String, Value>) -> TokenStream {
690    let unit = dimen.get("unit").unwrap();
691    let value = || dimen.get("value").unwrap().as_f64().unwrap() as f32;
692
693    match unit {
694        Value::String(ref unit) => match unit.as_ref() {
695            "auto" => quote!(taffy::style::Dimension::Auto),
696            "points" => {
697                let value = value();
698                quote!(taffy::style::Dimension::Points(#value))
699            }
700            "percent" => {
701                let value = value();
702                quote!(taffy::style::Dimension::Percent(#value))
703            }
704            _ => unreachable!(),
705        },
706        _ => unreachable!(),
707    }
708}
709
710fn generate_grid_auto_flow(auto_flow: &serde_json::Map<String, Value>) -> TokenStream {
711    let direction = auto_flow.get("direction").unwrap().as_str().unwrap();
712    let algorithm = auto_flow.get("algorithm").unwrap().as_str().unwrap();
713
714    match (direction, algorithm) {
715        ("row", "sparse") => quote!(taffy::style::GridAutoFlow::Row),
716        ("column", "sparse") => quote!(taffy::style::GridAutoFlow::Column),
717        ("row", "dense") => quote!(taffy::style::GridAutoFlow::RowDense),
718        ("column", "dense") => quote!(taffy::style::GridAutoFlow::ColumnDense),
719        _ => unreachable!(),
720    }
721}
722
723fn generate_line(start: TokenStream, end: TokenStream) -> TokenStream {
724    quote!(taffy::geometry::Line { start:#start, end:#end })
725}
726
727fn generate_grid_position(grid_position: &serde_json::Map<String, Value>) -> TokenStream {
728    let kind = grid_position.get("kind").unwrap();
729    let value = || grid_position.get("value").unwrap().as_f64().unwrap() as f32;
730
731    match kind {
732        Value::String(ref kind) => match kind.as_ref() {
733            "auto" => quote!(taffy::style::GridPlacement::Auto),
734            "span" => {
735                let value = value() as u16;
736                quote!(taffy::style::GridPlacement::Span(#value))
737            }
738            "line" => {
739                let value = value() as i16;
740                quote!(line(#value))
741            }
742            _ => unreachable!(),
743        },
744        _ => unreachable!(),
745    }
746}
747
748fn generate_track_definition_list(raw_list: &[Value]) -> TokenStream {
749    let list = raw_list.iter().map(|obj| match obj {
750        Value::Object(inner) => generate_track_definition(inner),
751        _ => unreachable!(),
752    });
753
754    quote!(vec![#(#list),*])
755}
756
757fn generate_track_definition(track_definition: &serde_json::Map<String, Value>) -> TokenStream {
758    let kind = track_definition.get("kind").unwrap().as_str().unwrap();
759    let name = || track_definition.get("name").unwrap().as_str().unwrap();
760    let arguments = || track_definition.get("arguments").unwrap();
761
762    match kind {
763        "scalar" => generate_scalar_definition(track_definition),
764        "function" => match (name(), arguments()) {
765            ("fit-content", Value::Array(arguments)) => {
766                if arguments.len() != 1 {
767                    panic!("fit-content function with the wrong number of arguments");
768                }
769                let argument = match arguments[0] {
770                    Value::Object(ref arg) => generate_scalar_definition(arg),
771                    _ => unreachable!(),
772                };
773                quote!(fit_content(#argument))
774            }
775            ("minmax", Value::Array(arguments)) => {
776                if arguments.len() != 2 {
777                    panic!("minmax function with the wrong number of arguments");
778                }
779                let min = match arguments[0] {
780                    Value::Object(ref arg) => generate_scalar_definition(arg),
781                    _ => unreachable!(),
782                };
783                let max = match arguments[1] {
784                    Value::Object(ref arg) => generate_scalar_definition(arg),
785                    _ => unreachable!(),
786                };
787                quote!(minmax(#min, #max))
788            }
789            ("repeat", Value::Array(arguments)) => {
790                if arguments.len() < 2 {
791                    panic!("repeat function with the wrong number of arguments");
792                }
793                let repetition = match arguments[0] {
794                    Value::Object(ref arg) => {
795                        let unit = arg.get("unit").unwrap().as_str().unwrap();
796                        let value = || arg.get("value").unwrap().as_u64().unwrap() as u16;
797
798                        match unit {
799                            "auto-fill" => quote!(GridTrackRepetition::AutoFill),
800                            "auto-fit" => quote!(GridTrackRepetition::AutoFit),
801                            "integer" => {
802                                let repetition_count = value();
803                                quote!(GridTrackRepetition::Count(#repetition_count))
804                            }
805                            _ => unreachable!(),
806                        }
807                    }
808                    _ => unreachable!(),
809                };
810                let track_list = generate_track_definition_list(&arguments[1..]);
811                quote!(repeat(#repetition, #track_list))
812            }
813            // TODO: Add support for fit-content
814            _ => unreachable!(),
815        },
816        _ => unreachable!(),
817    }
818}
819
820fn generate_scalar_definition(track_definition: &serde_json::Map<String, Value>) -> TokenStream {
821    let unit = || track_definition.get("unit").unwrap().as_str().unwrap();
822    let value = || track_definition.get("value").unwrap().as_f64().unwrap() as f32;
823
824    match unit() {
825        "auto" => quote!(auto()),
826        "min-content" => quote!(min_content()),
827        "max-content" => quote!(max_content()),
828        "points" | "percent" | "fraction" => {
829            let value = value();
830            match unit() {
831                "points" => quote!(points(#value)),
832                "percent" => quote!(percent(#value)),
833                "fraction" => quote!(flex(#value)),
834                _ => unreachable!(),
835            }
836        }
837        _ => unreachable!(),
838    }
839}
840
841fn generate_generic_measure_function() -> TokenStream {
842    quote!(
843        // WARNING: This enum is generated by the gentest script. Do not edit directly
844        #[allow(dead_code)]
845        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
846        enum WritingMode {
847            Horizontal,
848            Vertical,
849        }
850
851        // WARNING: This function is generated by the gentest script. Do not edit directly
852        #[allow(dead_code)]
853        fn measure_standard_text(
854            known_dimensions: taffy::geometry::Size<Option<f32>>,
855            available_space: taffy::geometry::Size<taffy::style::AvailableSpace>,
856            text_content: &str,
857            writing_mode: WritingMode,
858            _aspect_ratio: Option<f32>,
859        ) -> taffy::geometry::Size<f32> {
860            use taffy::axis::AbsoluteAxis;
861            use taffy::prelude::*;
862            const ZWS: char = '\u{200B}';
863            const H_WIDTH: f32 = 10.0;
864            const H_HEIGHT: f32 = 10.0;
865
866            if let Size { width: Some(width), height: Some(height) } = known_dimensions {
867                return Size { width, height };
868            }
869
870            let inline_axis = match writing_mode {
871                WritingMode::Horizontal => AbsoluteAxis::Horizontal,
872                WritingMode::Vertical => AbsoluteAxis::Vertical,
873            };
874            let block_axis = inline_axis.other_axis();
875
876            let lines: Vec<&str> = text_content.split(ZWS).collect();
877            if lines.is_empty() {
878                return Size::ZERO;
879            }
880
881            let min_line_length: usize = lines.iter().map(|line| line.len()).max().unwrap_or(0);
882            let max_line_length: usize = lines.iter().map(|line| line.len()).sum();
883            let inline_size =
884                known_dimensions.get_abs(inline_axis).unwrap_or_else(|| match available_space.get_abs(inline_axis) {
885                    AvailableSpace::MinContent => min_line_length as f32 * H_WIDTH,
886                    AvailableSpace::MaxContent => max_line_length as f32 * H_WIDTH,
887                    AvailableSpace::Definite(inline_size) => {
888                        inline_size.min(max_line_length as f32 * H_WIDTH).max(min_line_length as f32 * H_WIDTH)
889                    }
890                });
891            let block_size = known_dimensions.get_abs(block_axis).unwrap_or_else(|| {
892                let inline_line_length = (inline_size / H_WIDTH).floor() as usize;
893                let mut line_count = 1;
894                let mut current_line_length = 0;
895                for line in &lines {
896                    if current_line_length + line.len() > inline_line_length {
897                        if current_line_length > 0 {
898                            line_count += 1
899                        };
900                        current_line_length = line.len();
901                    } else {
902                        current_line_length += line.len();
903                    };
904                }
905                (line_count as f32) * H_HEIGHT
906            });
907
908            match writing_mode {
909                WritingMode::Horizontal => Size { width: inline_size, height: block_size },
910                WritingMode::Vertical => Size { width: block_size, height: inline_size },
911            }
912        }
913    )
914}
915
916fn generate_measure_function(text_content: &str, writing_mode: Option<&str>, aspect_ratio: Option<f32>) -> TokenStream {
917    let writing_mode_token = match writing_mode {
918        Some("vertical-rl" | "vertical-lr") => quote!(super::WritingMode::Vertical),
919        _ => quote!(super::WritingMode::Horizontal),
920    };
921
922    let aspect_ratio_token = match aspect_ratio {
923        Some(ratio) => quote!(Some(#ratio)),
924        None => quote!(None),
925    };
926
927    quote!(
928        taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
929            const TEXT : &str = #text_content;
930            super::measure_standard_text(known_dimensions, available_space, TEXT, #writing_mode_token, #aspect_ratio_token)
931        })
932    )
933}