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