tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 788HTML 740Markdown 12TOML 62536 Definitions

⎇ e8253b1ae8ad9e95777144ce91aa6fb1ec8d4e32 scripts/gentest/src/main.rs Rust · 886 lines · 35.1 KB History Who touched what raw

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