⎇ ae87fd0283feff32a5ceffe4c77394ff6ca88d39
scripts/gentest/src/main.rs
Historia
Quién tocó qué
crudo
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}