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