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