stylex_css/css/
generate_rtl.rs1use log::warn;
2
3use stylex_constants::constants::{
4 cursor_flip::CURSOR_FLIP,
5 logical_to_rtl::{INLINE_TO_RTL, LOGICAL_TO_RTL},
6};
7use stylex_enums::style_resolution::StyleResolution;
8use stylex_regex::regex::LENGTH_UNIT_TESTER_REGEX;
9use stylex_structures::{pair::Pair, stylex_state_options::StyleXStateOptions};
10
11fn logical_to_physical_rtl(input: &str) -> Option<&str> {
12 match input {
13 "start" | "inline-start" => Some("right"),
14 "end" | "inline-end" => Some("left"),
15 _ => None,
16 }
17}
18
19fn property_to_rtl(pair: &Pair, options: &StyleXStateOptions) -> Option<Pair> {
20 if let Some(<r_key) = LOGICAL_TO_RTL.get(pair.key.as_str()) {
21 return Some(Pair::new(ltr_key.to_string(), pair.value.clone()));
22 }
23
24 match pair.key.as_str() {
25 "float" | "clear" => logical_to_physical_rtl(pair.value.as_str())
26 .map(|value| Pair::new(pair.key.clone(), value.to_string())),
27 "background-position" => {
28 let new_val = pair
29 .value
30 .split_whitespace()
31 .map(|word| match word {
32 "start" | "inset-inline-start" => "right",
33 "end" | "inset-inline-end" => "left",
34 _ => word,
35 })
36 .collect::<Vec<_>>()
37 .join(" ");
38 Some(Pair::new(pair.key.clone(), new_val))
39 },
40 "cursor" => {
41 if !options.enable_legacy_value_flipping {
42 return None;
43 }
44
45 CURSOR_FLIP
46 .get(pair.value.as_str())
47 .map(|val| Pair::new(pair.key.clone(), val.to_string()))
48 },
49 _ => shadows_flip(pair.key.as_str(), pair.value.as_str(), options),
50 }
51}
52
53pub fn generate_rtl(pair: &Pair, options: &StyleXStateOptions) -> Option<Pair> {
54 let enable_logical_styles_polyfill = options.enable_logical_styles_polyfill;
55 let style_resolution = &options.style_resolution;
56 let key = pair.key.as_str();
57
58 if style_resolution == &StyleResolution::LegacyExpandShorthands {
59 if !enable_logical_styles_polyfill {
60 return None;
61 }
62 if let Some(inline_to_rtl_value) = INLINE_TO_RTL.get(key) {
63 return Some(Pair::new(
64 inline_to_rtl_value.to_string(),
65 pair.value.clone(),
66 ));
67 }
68 }
69
70 property_to_rtl(pair, options)
71}
72
73fn shadows_flip(key: &str, val: &str, options: &StyleXStateOptions) -> Option<Pair> {
74 match key {
75 "box-shadow" | "text-shadow" => {
76 if !options.enable_legacy_value_flipping {
77 return None;
78 }
79
80 let rtl_val = flip_shadow(val);
81 rtl_val.map(|rtl_val| Pair::new(key.to_string(), rtl_val.to_string()))
82 },
83 _ => None,
84 }
85}
86
87fn flip_shadow(value: &str) -> Option<String> {
88 let defs: Vec<&str> = value.split(',').collect();
89 let mut built_defs = Vec::new();
90
91 for def in defs {
92 let mut parts = def
93 .split_whitespace()
94 .map(|x| x.to_string())
95 .collect::<Vec<String>>();
96
97 let index = if is_unit(parts[0].as_str()) { 0 } else { 1 };
99
100 if index < parts.len() {
101 let flipped = flip_sign(&parts[index]);
102 parts[index] = flipped;
103 }
104 built_defs.push(parts.join(" "));
105 }
106
107 let rtl = built_defs.join(",");
108 if rtl != value { Some(rtl) } else { None }
109}
110
111fn is_unit(input: &str) -> bool {
112 LENGTH_UNIT_TESTER_REGEX
113 .is_match(input)
114 .unwrap_or_else(|err| {
115 warn!(
116 "Error matching LENGTH_UNIT_TESTER_REGEX for '{}': {}. Skipping pattern match.",
117 input, err
118 );
119
120 false
121 })
122}
123
124fn flip_sign(value: &str) -> String {
125 if value == "0" {
126 value.to_string()
127 } else if value.starts_with('-') {
128 value.strip_prefix('-').unwrap_or(value).to_string()
129 } else {
130 format!("-{}", value)
131 }
132}