gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5364Rust 86Markdown 12TOML 91841 Definitions

⎇ 01babab32d8138f3c3fa05095fb6c65fca0d3967 scripts/gentest/src/main.rs Rust · 879 lines · 33.4 KB History Who touched what raw

Make CSS Grid feature a default feature (#297) 01babab32 · 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 quote_number_prop(prop_name: &str, style: &Value, quoter: impl Fn(f32) -> TokenStream) -> TokenStream {
338        let prop_name_snake_case = prop_name.to_case(Case::Snake);
339        let prop_name_camel_case = prop_name.to_case(Case::Camel);
340        let prop_name_ident = format_ident!("{}", prop_name_snake_case);
341        match style[prop_name_camel_case] {
342            Value::Number(ref value) => {
343                let prop_value = quoter(value.as_f64().unwrap() as f32);
344                quote!(#prop_name_ident: #prop_value,)
345            }
346            _ => quote!(),
347        }
348    }
349
350    let display = match style["display"] {
351        Value::String(ref value) => match value.as_ref() {
352            "none" => quote!(display: taffy::style::Display::None,),
353            "grid" => quote!(display: taffy::style::Display::Grid,),
354            _ => quote!(display: taffy::style::Display::Flex,),
355        },
356        _ => quote!(),
357    };
358
359    let position_type = match style["positionType"] {
360        Value::String(ref value) => match value.as_ref() {
361            "absolute" => quote!(position_type: taffy::style::PositionType::Absolute,),
362            _ => quote!(),
363        },
364        _ => quote!(),
365    };
366
367    let direction = match style["direction"] {
368        Value::String(ref value) => match value.as_ref() {
369            "rtl" => quote!(direction: taffy::style::Direction::RTL,),
370            "ltr" => quote!(direction: taffy::style::Direction::LTR,),
371            _ => quote!(),
372        },
373        _ => quote!(),
374    };
375
376    let flex_direction = match style["flexDirection"] {
377        Value::String(ref value) => match value.as_ref() {
378            "row-reverse" => quote!(flex_direction: taffy::style::FlexDirection::RowReverse,),
379            "column" => quote!(flex_direction: taffy::style::FlexDirection::Column,),
380            "column-reverse" => quote!(flex_direction: taffy::style::FlexDirection::ColumnReverse,),
381            _ => quote!(),
382        },
383        _ => quote!(),
384    };
385
386    let flex_wrap = match style["flexWrap"] {
387        Value::String(ref value) => match value.as_ref() {
388            "wrap" => quote!(flex_wrap: taffy::style::FlexWrap::Wrap,),
389            "wrap-reverse" => quote!(flex_wrap: taffy::style::FlexWrap::WrapReverse,),
390            _ => quote!(),
391        },
392        _ => quote!(),
393    };
394
395    let overflow = match style["overflow"] {
396        Value::String(ref value) => match value.as_ref() {
397            "hidden" => quote!(overflow: taffy::style::Overflow::Hidden,),
398            "scroll" => quote!(overflow: taffy::style::Overflow::Scroll,),
399            _ => quote!(),
400        },
401        _ => quote!(),
402    };
403
404    let align_items = match style["alignItems"] {
405        Value::String(ref value) => match value.as_ref() {
406            "flex-start" | "start" => quote!(align_items: Some(taffy::style::AlignItems::Start),),
407            "flex-end" | "end" => quote!(align_items: Some(taffy::style::AlignItems::End),),
408            "center" => quote!(align_items: Some(taffy::style::AlignItems::Center),),
409            "baseline" => quote!(align_items: Some(taffy::style::AlignItems::Baseline),),
410            "stretch" => quote!(align_items: Some(taffy::style::AlignItems::Stretch),),
411            _ => quote!(),
412        },
413        _ => quote!(),
414    };
415
416    let align_self = match style["alignSelf"] {
417        Value::String(ref value) => match value.as_ref() {
418            "flex-start" | "start" => quote!(align_self: Some(taffy::style::AlignSelf::Start),),
419            "flex-end" | "end" => quote!(align_self: Some(taffy::style::AlignSelf::End),),
420            "center" => quote!(align_self: Some(taffy::style::AlignSelf::Center),),
421            "baseline" => quote!(align_self: Some(taffy::style::AlignSelf::Baseline),),
422            "stretch" => quote!(align_self: Some(taffy::style::AlignSelf::Stretch),),
423            _ => quote!(),
424        },
425        _ => quote!(),
426    };
427
428    let justify_items = match style["justifyItems"] {
429        Value::String(ref value) => match value.as_ref() {
430            "flex-start" | "start" => quote!(justify_items: Some(taffy::style::JustifyItems::Start),),
431            "flex-end" | "end" => quote!(justify_items: Some(taffy::style::JustifyItems::End),),
432            "center" => quote!(justify_items: Some(taffy::style::JustifyItems::Center),),
433            "baseline" => quote!(justify_items: Some(taffy::style::JustifyItems::Baseline),),
434            "stretch" => quote!(justify_items: Some(taffy::style::JustifyItems::Stretch),),
435            _ => quote!(),
436        },
437        _ => quote!(),
438    };
439
440    let justify_self = match style["justifySelf"] {
441        Value::String(ref value) => match value.as_ref() {
442            "flex-start" | "start" => quote!(justify_self: Some(taffy::style::JustifySelf::Start),),
443            "flex-end" | "end" => quote!(justify_self: Some(taffy::style::JustifySelf::End),),
444            "center" => quote!(justify_self: Some(taffy::style::JustifySelf::Center),),
445            "baseline" => quote!(justify_self: Some(taffy::style::JustifySelf::Baseline),),
446            "stretch" => quote!(justify_self: Some(taffy::style::JustifySelf::Stretch),),
447            _ => quote!(),
448        },
449        _ => quote!(),
450    };
451
452    let align_content = match style["alignContent"] {
453        Value::String(ref value) => match value.as_ref() {
454            "flex-start" | "start" => quote!(align_content: Some(taffy::style::AlignContent::Start),),
455            "flex-end" | "end" => quote!(align_content: Some(taffy::style::AlignContent::End),),
456            "center" => quote!(align_content: Some(taffy::style::AlignContent::Center),),
457            "stretch" => quote!(align_content: Some(taffy::style::AlignContent::Stretch),),
458            "space-between" => quote!(align_content: Some(taffy::style::AlignContent::SpaceBetween),),
459            "space-around" => quote!(align_content: Some(taffy::style::AlignContent::SpaceAround),),
460            "space-evenly" => quote!(align_content: Some(taffy::style::AlignContent::SpaceEvenly),),
461            _ => quote!(),
462        },
463        _ => quote!(),
464    };
465
466    let justify_content = match style["justifyContent"] {
467        Value::String(ref value) => match value.as_ref() {
468            "flex-start" | "start" => quote!(justify_content: Some(taffy::style::JustifyContent::Start),),
469            "flex-end" | "end" => quote!(justify_content: Some(taffy::style::JustifyContent::End),),
470            "center" => quote!(justify_content: Some(taffy::style::JustifyContent::Center),),
471            "stretch" => quote!(align_content: Some(taffy::style::AlignContent::Stretch),),
472            "space-between" => quote!(justify_content: Some(taffy::style::JustifyContent::SpaceBetween),),
473            "space-around" => quote!(justify_content: Some(taffy::style::JustifyContent::SpaceAround),),
474            "space-evenly" => quote!(justify_content: Some(taffy::style::JustifyContent::SpaceEvenly),),
475            _ => quote!(),
476        },
477        _ => quote!(),
478    };
479
480    let flex_grow = quote_number_prop("flex_grow", style, |value: f32| quote!(#value));
481    let flex_shrink = quote_number_prop("flex_shrink", style, |value: f32| quote!(#value));
482
483    let flex_basis = quote_object_prop("flex_basis", style, generate_dimension);
484    let size = quote_object_prop("size", style, generate_size);
485    let min_size = quote_object_prop("min_size", style, generate_size);
486    let max_size = quote_object_prop("max_size", style, generate_size);
487
488    let gap = quote_object_prop("gap", style, generate_gap);
489
490    let grid_template_rows = quote_array_prop("grid_template_rows", style, generate_track_definition_list);
491    let grid_template_columns = quote_array_prop("grid_template_columns", style, generate_track_definition_list);
492    let grid_auto_rows = quote_array_prop("grid_auto_rows", style, generate_track_definition_list);
493    let grid_auto_columns = quote_array_prop("grid_auto_columns", style, generate_track_definition_list);
494    let grid_auto_flow = quote_object_prop("grid_auto_flow", style, generate_grid_auto_flow);
495
496    let default_grid_placement = quote!(taffy::style::GridPlacement::Auto);
497
498    let grid_row_start = quote_object_value("grid_row_start", style, generate_grid_position);
499    let grid_row_end = quote_object_value("grid_row_end", style, generate_grid_position);
500    let grid_row = if grid_row_start.is_some() || grid_row_end.is_some() {
501        quote_prop(
502            "grid_row",
503            generate_line(
504                grid_row_start.unwrap_or(default_grid_placement.clone()),
505                grid_row_end.unwrap_or(default_grid_placement.clone()),
506            ),
507        )
508    } else {
509        quote!()
510    };
511
512    let grid_column_start = quote_object_value("grid_column_start", style, generate_grid_position);
513    let grid_column_end = quote_object_value("grid_column_end", style, generate_grid_position);
514    let grid_column = if grid_column_start.is_some() || grid_column_end.is_some() {
515        quote_prop(
516            "grid_column",
517            generate_line(
518                grid_column_start.unwrap_or(default_grid_placement.clone()),
519                grid_column_end.unwrap_or(default_grid_placement),
520            ),
521        )
522    } else {
523        quote!()
524    };
525
526    let measure_func: Option<_> = get_string_value("text_content", node).map(generate_measure_function);
527
528    edges_quoted!(style, margin, generate_length_percentage_auto, quote!(zero()));
529    edges_quoted!(style, padding, generate_length_percentage, quote!(zero()));
530    edges_quoted!(style, border, generate_length_percentage, quote!(zero()));
531    edges_quoted!(style, position, generate_length_percentage_auto, quote!(auto()));
532
533    // Quote children
534    let child_descriptions: Vec<Value> = match node["children"] {
535        Value::Array(ref value) => value.clone(),
536        _ => vec![],
537    };
538    let has_children = !child_descriptions.is_empty();
539    let (children_body, children) = if has_children {
540        let body = child_descriptions
541            .iter()
542            .enumerate()
543            .map(|(i, child)| generate_node(&format!("{ident}{i}"), child))
544            .collect();
545        let idents = child_descriptions
546            .iter()
547            .enumerate()
548            .map(|(i, _)| Ident::new(&format!("{ident}{i}"), Span::call_site()))
549            .collect::<Vec<_>>();
550        (body, quote!(&[#(#idents),*]))
551    } else {
552        (quote!(), quote!())
553    };
554
555    let ident = Ident::new(ident, Span::call_site());
556
557    let style = quote!(taffy::style::Style {
558        #display
559        #direction
560        #position_type
561        #flex_direction
562        #flex_wrap
563        #overflow
564        #align_items
565        #align_self
566        #justify_items
567        #justify_self
568        #align_content
569        #justify_content
570        #flex_grow
571        #flex_shrink
572        #flex_basis
573        #gap
574        #grid_template_rows
575        #grid_template_columns
576        #grid_auto_rows
577        #grid_auto_columns
578        #grid_auto_flow
579        #grid_row
580        #grid_column
581        #size
582        #min_size
583        #max_size
584        #margin
585        #padding
586        #position
587        #border
588        ..Default::default()
589    });
590
591    if has_children {
592        quote!(
593            #children_body
594            let #ident = taffy.new_with_children(#style,#children).unwrap();
595        )
596    } else if measure_func.is_some() {
597        quote!(let #ident = taffy.new_leaf_with_measure(#style,#measure_func,).unwrap();)
598    } else {
599        quote!(let #ident = taffy.new_leaf(#style).unwrap();)
600    }
601}
602
603fn generate_size(size: &serde_json::Map<String, Value>) -> TokenStream {
604    dim_quoted!(size, width, generate_dimension, quote!(auto()));
605    dim_quoted!(size, height, generate_dimension, quote!(auto()));
606    quote!(
607        taffy::geometry::Size {
608            #width #height
609        }
610    )
611}
612
613fn generate_gap(size: &serde_json::Map<String, Value>) -> TokenStream {
614    dim_quoted_renamed!(size, column, width, generate_length_percentage, quote!(zero()));
615    dim_quoted_renamed!(size, row, height, generate_length_percentage, quote!(zero()));
616    quote!(
617        taffy::geometry::Size {
618            #width #height
619        }
620    )
621}
622
623fn generate_length_percentage(dimen: &serde_json::Map<String, Value>) -> TokenStream {
624    let unit = dimen.get("unit").unwrap();
625    let value = || dimen.get("value").unwrap().as_f64().unwrap() as f32;
626
627    match unit {
628        Value::String(ref unit) => match unit.as_ref() {
629            "points" => {
630                let value = value();
631                quote!(taffy::style::LengthPercentage::Points(#value))
632            }
633            "percent" => {
634                let value = value();
635                quote!(taffy::style::LengthPercentage::Percent(#value))
636            }
637            _ => unreachable!(),
638        },
639        _ => unreachable!(),
640    }
641}
642
643fn generate_length_percentage_auto(dimen: &serde_json::Map<String, Value>) -> TokenStream {
644    let unit = dimen.get("unit").unwrap();
645    let value = || dimen.get("value").unwrap().as_f64().unwrap() as f32;
646
647    match unit {
648        Value::String(ref unit) => match unit.as_ref() {
649            "auto" => quote!(taffy::style::LengthPercentageAuto::Auto),
650            "points" => {
651                let value = value();
652                quote!(taffy::style::LengthPercentageAuto::Points(#value))
653            }
654            "percent" => {
655                let value = value();
656                quote!(taffy::style::LengthPercentageAuto::Percent(#value))
657            }
658            _ => unreachable!(),
659        },
660        _ => unreachable!(),
661    }
662}
663
664fn generate_dimension(dimen: &serde_json::Map<String, Value>) -> TokenStream {
665    let unit = dimen.get("unit").unwrap();
666    let value = || dimen.get("value").unwrap().as_f64().unwrap() as f32;
667
668    match unit {
669        Value::String(ref unit) => match unit.as_ref() {
670            "auto" => quote!(taffy::style::Dimension::Auto),
671            "points" => {
672                let value = value();
673                quote!(taffy::style::Dimension::Points(#value))
674            }
675            "percent" => {
676                let value = value();
677                quote!(taffy::style::Dimension::Percent(#value))
678            }
679            _ => unreachable!(),
680        },
681        _ => unreachable!(),
682    }
683}
684
685fn generate_grid_auto_flow(auto_flow: &serde_json::Map<String, Value>) -> TokenStream {
686    let direction = auto_flow.get("direction").unwrap().as_str().unwrap();
687    let algorithm = auto_flow.get("algorithm").unwrap().as_str().unwrap();
688
689    match (direction, algorithm) {
690        ("row", "sparse") => quote!(taffy::style::GridAutoFlow::Row),
691        ("column", "sparse") => quote!(taffy::style::GridAutoFlow::Column),
692        ("row", "dense") => quote!(taffy::style::GridAutoFlow::RowDense),
693        ("column", "dense") => quote!(taffy::style::GridAutoFlow::ColumnDense),
694        _ => unreachable!(),
695    }
696}
697
698fn generate_line(start: TokenStream, end: TokenStream) -> TokenStream {
699    quote!(taffy::geometry::Line { start:#start, end:#end })
700}
701
702fn generate_grid_position(grid_position: &serde_json::Map<String, Value>) -> TokenStream {
703    let kind = grid_position.get("kind").unwrap();
704    let value = || grid_position.get("value").unwrap().as_f64().unwrap() as f32;
705
706    match kind {
707        Value::String(ref kind) => match kind.as_ref() {
708            "auto" => quote!(taffy::style::GridPlacement::Auto),
709            "span" => {
710                let value = value() as u16;
711                quote!(taffy::style::GridPlacement::Span(#value))
712            }
713            "line" => {
714                let value = value() as i16;
715                quote!(taffy::style::GridPlacement::Line(#value))
716            }
717            _ => unreachable!(),
718        },
719        _ => unreachable!(),
720    }
721}
722
723fn generate_track_definition_list(raw_list: &[Value]) -> TokenStream {
724    let list = raw_list.iter().map(|obj| match obj {
725        Value::Object(inner) => generate_track_definition(inner),
726        _ => unreachable!(),
727    });
728
729    quote!(vec![#(#list),*])
730}
731
732fn generate_track_definition(track_definition: &serde_json::Map<String, Value>) -> TokenStream {
733    let kind = track_definition.get("kind").unwrap().as_str().unwrap();
734    let name = || track_definition.get("name").unwrap().as_str().unwrap();
735    let arguments = || track_definition.get("arguments").unwrap();
736
737    match kind {
738        "scalar" => generate_scalar_definition(track_definition),
739        "function" => match (name(), arguments()) {
740            ("fit-content", Value::Array(arguments)) => {
741                if arguments.len() != 1 {
742                    panic!("fit-content function with the wrong number of arguments");
743                }
744                let argument = match arguments[0] {
745                    Value::Object(ref arg) => generate_scalar_definition(arg),
746                    _ => unreachable!(),
747                };
748                quote!(fit_content(#argument))
749            }
750            ("minmax", Value::Array(arguments)) => {
751                if arguments.len() != 2 {
752                    panic!("minmax function with the wrong number of arguments");
753                }
754                let min = match arguments[0] {
755                    Value::Object(ref arg) => generate_scalar_definition(arg),
756                    _ => unreachable!(),
757                };
758                let max = match arguments[1] {
759                    Value::Object(ref arg) => generate_scalar_definition(arg),
760                    _ => unreachable!(),
761                };
762                quote!(minmax(#min, #max))
763            }
764            ("repeat", Value::Array(arguments)) => {
765                if arguments.len() < 2 {
766                    panic!("repeat function with the wrong number of arguments");
767                }
768                let repetition = match arguments[0] {
769                    Value::Object(ref arg) => {
770                        let unit = arg.get("unit").unwrap().as_str().unwrap();
771                        let value = || arg.get("value").unwrap().as_u64().unwrap() as u16;
772
773                        match unit {
774                            "auto-fill" => quote!(GridTrackRepetition::AutoFill),
775                            // Not yet implemented in taffy
776                            "auto-fit" => quote!(GridTrackRepetition::AutoFit),
777                            // Not yet implemented in taffy
778                            "integer" => {
779                                let repetition_count = value();
780                                quote!(GridTrackRepetition::Count(#repetition_count))
781                            }
782                            _ => unreachable!(),
783                        }
784                    }
785                    _ => unreachable!(),
786                };
787                let track_list = generate_track_definition_list(&arguments[1..]);
788                quote!(repeat(#repetition, #track_list))
789            }
790            // TODO: Add support for fit-content
791            _ => unreachable!(),
792        },
793        _ => unreachable!(),
794    }
795}
796
797fn generate_scalar_definition(track_definition: &serde_json::Map<String, Value>) -> TokenStream {
798    let unit = || track_definition.get("unit").unwrap().as_str().unwrap();
799    let value = || track_definition.get("value").unwrap().as_f64().unwrap() as f32;
800
801    match unit() {
802        "auto" => quote!(auto()),
803        "min-content" => quote!(min_content()),
804        "max-content" => quote!(max_content()),
805        "points" | "percent" | "fraction" => {
806            let value = value();
807            match unit() {
808                "points" => quote!(points(#value)),
809                "percent" => quote!(percent(#value)),
810                "fraction" => quote!(flex(#value)),
811                _ => unreachable!(),
812            }
813        }
814        _ => unreachable!(),
815    }
816}
817
818fn generate_generic_measure_function() -> TokenStream {
819    quote!(
820        // WARNING: This function is generated by the gentest script. Do not edit directly
821        #[allow(dead_code)]
822        fn measure_standard_text(
823            known_dimensions: taffy::geometry::Size<Option<f32>>,
824            available_space: taffy::geometry::Size<taffy::style::AvailableSpace>,
825            text_content: &str,
826        ) -> taffy::geometry::Size<f32> {
827            use taffy::prelude::*;
828            const ZWS: char = '\u{200B}';
829            const H_WIDTH: f32 = 10.0;
830            const H_HEIGHT: f32 = 10.0;
831
832            if let Size { width: Some(width), height: Some(height) } = known_dimensions {
833                return Size { width, height };
834            }
835
836            let lines: Vec<&str> = text_content.split(ZWS).collect();
837            if lines.is_empty() {
838                return Size::ZERO;
839            }
840
841            let min_line_length: usize = lines.iter().map(|line| line.len()).max().unwrap_or(0);
842            let max_line_length: usize = lines.iter().map(|line| line.len()).sum();
843            let width = known_dimensions.width.unwrap_or_else(|| match available_space.width {
844                AvailableSpace::MinContent => min_line_length as f32 * H_WIDTH,
845                AvailableSpace::MaxContent => max_line_length as f32 * H_WIDTH,
846                AvailableSpace::Definite(width) => {
847                    width.min(max_line_length as f32 * H_WIDTH).max(min_line_length as f32 * H_WIDTH)
848                }
849            });
850            let height = known_dimensions.height.unwrap_or_else(|| {
851                let width_line_length = (width / H_WIDTH).floor() as usize;
852                let mut line_count = 1;
853                let mut current_line_length = 0;
854                for line in &lines {
855                    if current_line_length + line.len() > width_line_length {
856                        if current_line_length > 0 {
857                            line_count += 1
858                        };
859                        current_line_length = line.len();
860                    } else {
861                        current_line_length += line.len();
862                    };
863                }
864                (line_count as f32) * H_HEIGHT
865            });
866
867            Size { width, height }
868        }
869    )
870}
871
872fn generate_measure_function(text_content: &str) -> TokenStream {
873    quote!(
874        taffy::node::MeasureFunc::Raw(|known_dimensions, available_space| {
875            const TEXT : &str = #text_content;
876            super::measure_standard_text(known_dimensions, available_space, TEXT)
877        })
878    )
879}