tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1311HTML 640Markdown 11TOML 63653 Definiciones

⎇ ae87fd0283feff32a5ceffe4c77394ff6ca88d39 scripts/gentest/src/main.rs Rust · 918 líneas · 36.3 KB Historia Quién tocó qué crudo

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