1use super::*;
4
5pub fn resolve_css_var(value: &str, vars: &BTreeMap<String, String>) -> String {
11 resolve_css_var_depth(value, vars, 0)
12}
13
14pub(crate) fn resolve_css_var_depth(value: &str, vars: &BTreeMap<String, String>, depth: u32) -> String {
15 if depth >= 8 {
16 return String::from(value);
17 }
18 let mut result = String::new();
19 let mut rest = value;
20 while let Some(start) = rest.find("var(") {
21 result.push_str(rest.get(..start).unwrap_or(""));
22 let after = rest.get(start + 4..).unwrap_or("");
23 let mut depth_paren = 1usize;
25 let mut end = 0;
26 for (i, c) in after.char_indices() {
27 match c {
28 '(' => depth_paren += 1,
29 ')' => {
30 depth_paren -= 1;
31 if depth_paren == 0 {
32 end = i;
33 break;
34 }
35 }
36 _ => {}
37 }
38 }
39 let inner = after.get(..end).unwrap_or("");
40 let (var_name, fallback) = if let Some(comma_pos) = inner.find(',') {
42 (
43 inner.get(..comma_pos).unwrap_or("").trim(),
44 Some(inner.get(comma_pos + 1..).unwrap_or("").trim()),
45 )
46 } else {
47 (inner.trim(), None)
48 };
49 let resolved = vars
50 .get(var_name)
51 .cloned()
52 .or_else(|| fallback.map(|s| s.to_string()))
53 .unwrap_or_default();
54 let resolved = if resolved.contains("var(") {
56 resolve_css_var_depth(&resolved, vars, depth + 1)
57 } else {
58 resolved
59 };
60 result.push_str(&resolved);
61 rest = after.get(end + 1..).unwrap_or("");
62 }
63 result.push_str(rest);
64 result
65}
66
67pub fn resolve_css_env(value: &str) -> String {
75 let mut result = String::new();
76 let mut rest = value;
77 while let Some(start) = rest.find("env(") {
78 result.push_str(rest.get(..start).unwrap_or(""));
79 let after = rest.get(start + 4..).unwrap_or("");
80 let mut depth_paren = 1usize;
81 let mut end = 0;
82 for (i, c) in after.char_indices() {
83 match c {
84 '(' => depth_paren += 1,
85 ')' => {
86 depth_paren -= 1;
87 if depth_paren == 0 {
88 end = i;
89 break;
90 }
91 }
92 _ => {}
93 }
94 }
95 let inner = after.get(..end).unwrap_or("");
96 let (_env_name, fallback) = if let Some(comma_pos) = inner.find(',') {
97 (
98 inner.get(..comma_pos).unwrap_or("").trim(),
99 Some(inner.get(comma_pos + 1..).unwrap_or("").trim()),
100 )
101 } else {
102 (inner.trim(), None)
103 };
104 let resolved = fallback.unwrap_or("0px").to_string();
105 let resolved = if resolved.contains("env(") {
106 resolve_css_env(&resolved)
107 } else {
108 resolved
109 };
110 result.push_str(&resolved);
111 rest = after.get(end + 1..).unwrap_or("");
112 }
113 result.push_str(rest);
114 result
115}
116
117fn parse_calc_unit(s: &str, containing_width: i32) -> Option<f32> {
119 let s = s.trim();
120 if s.is_empty() {
121 return None;
122 }
123 if let Some(n) = s.strip_suffix("px") {
124 return n.trim().parse::<f32>().ok();
125 }
126 if let Some(n) = s.strip_suffix("vh") {
134 let h = crate::os_lib::layout::VIEWPORT_HINT_H.load(core::sync::atomic::Ordering::Relaxed);
135 return n.trim().parse::<f32>().ok().map(|v| v / 100.0 * h as f32);
136 }
137 if let Some(n) = s.strip_suffix("vw") {
138 let w = crate::os_lib::layout::VIEWPORT_HINT_W.load(core::sync::atomic::Ordering::Relaxed);
139 return n.trim().parse::<f32>().ok().map(|v| v / 100.0 * w as f32);
140 }
141 if let Some(n) = s.strip_suffix("vmin") {
142 let w = crate::os_lib::layout::VIEWPORT_HINT_W.load(core::sync::atomic::Ordering::Relaxed);
143 let h = crate::os_lib::layout::VIEWPORT_HINT_H.load(core::sync::atomic::Ordering::Relaxed);
144 return n.trim().parse::<f32>().ok().map(|v| v / 100.0 * w.min(h) as f32);
145 }
146 if let Some(n) = s.strip_suffix("vmax") {
147 let w = crate::os_lib::layout::VIEWPORT_HINT_W.load(core::sync::atomic::Ordering::Relaxed);
148 let h = crate::os_lib::layout::VIEWPORT_HINT_H.load(core::sync::atomic::Ordering::Relaxed);
149 return n.trim().parse::<f32>().ok().map(|v| v / 100.0 * w.max(h) as f32);
150 }
151 if let Some(n) = s.strip_suffix("rem") {
152 return n.trim().parse::<f32>().ok().map(|v| v * 16.0);
153 }
154 if let Some(n) = s.strip_suffix("em") {
155 return n.trim().parse::<f32>().ok().map(|v| v * 16.0);
156 }
157 if let Some(n) = s.strip_suffix('%') {
158 return n
159 .trim()
160 .parse::<f32>()
161 .ok()
162 .map(|v| v / 100.0 * containing_width as f32);
163 }
164 if let Some(n) = s.strip_suffix("in") {
165 return n.trim().parse::<f32>().ok().map(|v| v * 96.0);
166 }
167 if let Some(n) = s.strip_suffix("cm") {
168 return n.trim().parse::<f32>().ok().map(|v| v * 96.0 / 2.54);
169 }
170 if let Some(n) = s.strip_suffix("mm") {
171 return n.trim().parse::<f32>().ok().map(|v| v * 96.0 / 25.4);
172 }
173 if let Some(n) = s.strip_suffix("pt") {
174 return n.trim().parse::<f32>().ok().map(|v| v * 96.0 / 72.0);
175 }
176 if let Some(n) = s.strip_suffix("pc") {
177 return n.trim().parse::<f32>().ok().map(|v| v * 16.0);
178 }
179 if let Some(n) = s.strip_suffix('q') {
180 return n.trim().parse::<f32>().ok().map(|v| v * 96.0 / 101.6);
181 }
182 s.parse::<f32>().ok()
183}
184
185fn calc_skip_ws(bytes: &[u8], pos: &mut usize) {
187 while *pos < bytes.len() && bytes[*pos] == b' ' {
188 *pos += 1;
189 }
190}
191
192fn parse_calc_factor(s: &str, pos: &mut usize, containing_width: i32) -> Option<f32> {
196 let bytes = s.as_bytes();
197 calc_skip_ws(bytes, pos);
198 if bytes.get(*pos) == Some(&b'(') {
199 *pos += 1;
200 let start = *pos;
201 let mut depth = 1i32;
202 while *pos < bytes.len() && depth > 0 {
203 match bytes[*pos] {
204 b'(' => depth += 1,
205 b')' => depth -= 1,
206 _ => {}
207 }
208 *pos += 1;
209 }
210 if depth != 0 {
211 return None;
212 }
213 let inner = s.get(start..pos.saturating_sub(1)).unwrap_or("");
215 return parse_calc_expr(inner.trim(), containing_width);
216 }
217 let start = *pos;
218 while *pos < bytes.len() {
219 let c = bytes[*pos];
220 if c == b' ' || c == b'(' || c == b')' {
221 break;
222 }
223 if (c == b'+' || c == b'-') && *pos > start {
227 break;
228 }
229 *pos += 1;
230 }
231 let token = s.get(start..*pos).unwrap_or("");
232 parse_calc_unit(token, containing_width)
233}
234
235fn parse_calc_term(s: &str, pos: &mut usize, containing_width: i32) -> Option<f32> {
237 let bytes = s.as_bytes();
238 let mut value = parse_calc_factor(s, pos, containing_width)?;
239 loop {
240 calc_skip_ws(bytes, pos);
241 match bytes.get(*pos) {
242 Some(b'*') => {
243 *pos += 1;
244 calc_skip_ws(bytes, pos);
245 let rhs = parse_calc_factor(s, pos, containing_width)?;
246 value *= rhs;
247 }
248 Some(b'/') => {
249 *pos += 1;
250 calc_skip_ws(bytes, pos);
251 let rhs = parse_calc_factor(s, pos, containing_width)?;
252 if rhs == 0.0 {
253 return None;
254 }
255 value /= rhs;
256 }
257 _ => break,
258 }
259 }
260 Some(value)
261}
262
263fn parse_calc_expr(s: &str, containing_width: i32) -> Option<f32> {
269 let bytes = s.as_bytes();
270 let mut pos = 0usize;
271 let mut value = parse_calc_term(s, &mut pos, containing_width)?;
272 loop {
273 calc_skip_ws(bytes, &mut pos);
274 match bytes.get(pos) {
275 Some(b'+') => {
276 pos += 1;
277 calc_skip_ws(bytes, &mut pos);
278 let rhs = parse_calc_term(s, &mut pos, containing_width)?;
279 value += rhs;
280 }
281 Some(b'-') => {
282 pos += 1;
283 calc_skip_ws(bytes, &mut pos);
284 let rhs = parse_calc_term(s, &mut pos, containing_width)?;
285 value -= rhs;
286 }
287 _ => break,
288 }
289 }
290 calc_skip_ws(bytes, &mut pos);
291 if pos != bytes.len() {
292 return None;
294 }
295 Some(value)
296}
297
298pub fn eval_calc(expr: &str, containing_width: i32) -> Option<i32> {
303 let inner = expr.trim();
304 let inner = inner
305 .strip_prefix("calc(")
306 .and_then(|s| s.strip_suffix(')'))?;
307 parse_calc_expr(inner.trim(), containing_width).map(|v| v as i32)
308}
309
310pub(crate) fn split_top_level_commas(s: &str) -> alloc::vec::Vec<&str> {
314 let mut parts = alloc::vec::Vec::new();
315 let bytes = s.as_bytes();
316 let mut depth = 0i32;
317 let mut start = 0usize;
318 for (i, &b) in bytes.iter().enumerate() {
319 match b {
320 b'(' => depth += 1,
321 b')' => depth -= 1,
322 b',' if depth == 0 => {
323 parts.push(s.get(start..i).unwrap_or("").trim());
324 start = i + 1;
325 }
326 _ => {}
327 }
328 }
329 parts.push(s.get(start..).unwrap_or("").trim());
330 parts
331}
332
333pub(crate) fn parse_one_declaration(raw: &str) -> Option<Declaration> {
336 let mut kv = raw.splitn(2, ':');
337 let name = kv.next()?.trim();
338 let raw_value = kv.next()?.trim();
339 let (value, important) = match raw_value.rfind('!') {
340 Some(pos)
341 if raw_value
342 .get(pos + 1..)
343 .unwrap_or("")
344 .trim()
345 .eq_ignore_ascii_case("important") =>
346 {
347 (raw_value.get(..pos).unwrap_or("").trim(), true)
348 }
349 _ => (raw_value, false),
350 };
351 if name.is_empty() || value.is_empty() {
352 return None;
353 }
354 Some(Declaration {
355 name: String::from(name),
356 value: String::from(value),
357 important,
358 })
359}
360
361pub(crate) fn parse_nested_body(
367 parent_selector_texts: &[String],
368 body: &str,
369 out_rules: &mut Vec<Rule>,
370 layer: Option<usize>,
371) -> Vec<Declaration> {
372 let mut declarations = Vec::new();
373 let chars: Vec<char> = body.chars().collect();
374 let mut i = 0usize;
375 let mut buf = String::new();
376 while i < chars.len() {
377 match chars[i] {
378 '{' => {
379 let nested_selector_text = buf.trim().to_string();
380 buf.clear();
381 let mut depth = 1i32;
383 let start = i + 1;
384 let mut j = start;
385 while j < chars.len() && depth > 0 {
386 match chars[j] {
387 '{' => depth += 1,
388 '}' => depth -= 1,
389 _ => {}
390 }
391 if depth > 0 {
392 j += 1;
393 }
394 }
395 let inner_body: String = chars[start..j.min(chars.len())].iter().collect();
396 i = j;
397 if !nested_selector_text.is_empty() {
398 let mut resolved_texts: Vec<String> = Vec::new();
399 for nb in split_top_level_commas(&nested_selector_text) {
400 let nb = nb.trim();
401 if nb.is_empty() {
402 continue;
403 }
404 for parent in parent_selector_texts {
405 let resolved = if nb.contains('&') {
406 nb.replace('&', parent)
407 } else {
408 alloc::format!("{} {}", parent, nb)
409 };
410 resolved_texts.push(resolved);
411 }
412 }
413 let nested_selectors: Vec<Selector> = resolved_texts
414 .iter()
415 .filter_map(|s| parse_selector(s.trim()))
416 .collect();
417 let nested_decls =
418 parse_nested_body(&resolved_texts, &inner_body, out_rules, layer);
419 if !nested_selectors.is_empty() && !nested_decls.is_empty() {
420 out_rules.push(Rule {
421 selectors: nested_selectors,
422 declarations: nested_decls,
423 layer,
424 media_query: None,
425 container_query: None,
426 });
427 }
428 }
429 }
430 ';' => {
431 if let Some(d) = parse_one_declaration(&buf) {
432 declarations.push(d);
433 }
434 buf.clear();
435 }
436 c => buf.push(c),
437 }
438 i += 1;
439 }
440 if let Some(d) = parse_one_declaration(&buf) {
442 declarations.push(d);
443 }
444 declarations
445}
446
447pub(crate) fn eval_length_token(s: &str, containing_width: i32) -> Option<f32> {
450 let t = s.trim();
451 if t.starts_with("calc(") || t.starts_with("min(") || t.starts_with("max(") || t.starts_with("clamp(")
452 {
453 return eval_css_math(t, containing_width).map(|v| v as f32);
454 }
455 if let Some(n) = t.strip_suffix("px") {
456 return n.trim().parse::<f32>().ok();
457 }
458 if let Some(n) = t.strip_suffix("vh") {
469 let h = crate::os_lib::layout::VIEWPORT_HINT_H.load(core::sync::atomic::Ordering::Relaxed);
470 return n.trim().parse::<f32>().ok().map(|v| v / 100.0 * h as f32);
471 }
472 if let Some(n) = t.strip_suffix("vw") {
473 let w = crate::os_lib::layout::VIEWPORT_HINT_W.load(core::sync::atomic::Ordering::Relaxed);
474 return n.trim().parse::<f32>().ok().map(|v| v / 100.0 * w as f32);
475 }
476 if let Some(n) = t.strip_suffix("vmin") {
477 let w = crate::os_lib::layout::VIEWPORT_HINT_W.load(core::sync::atomic::Ordering::Relaxed);
478 let h = crate::os_lib::layout::VIEWPORT_HINT_H.load(core::sync::atomic::Ordering::Relaxed);
479 return n.trim().parse::<f32>().ok().map(|v| v / 100.0 * w.min(h) as f32);
480 }
481 if let Some(n) = t.strip_suffix("vmax") {
482 let w = crate::os_lib::layout::VIEWPORT_HINT_W.load(core::sync::atomic::Ordering::Relaxed);
483 let h = crate::os_lib::layout::VIEWPORT_HINT_H.load(core::sync::atomic::Ordering::Relaxed);
484 return n.trim().parse::<f32>().ok().map(|v| v / 100.0 * w.max(h) as f32);
485 }
486 if let Some(n) = t.strip_suffix("rem") {
487 return n.trim().parse::<f32>().ok().map(|v| v * 16.0);
488 }
489 if let Some(n) = t.strip_suffix("em") {
490 return n.trim().parse::<f32>().ok().map(|v| v * 16.0);
491 }
492 if let Some(n) = t.strip_suffix('%') {
493 return n
494 .trim()
495 .parse::<f32>()
496 .ok()
497 .map(|v| v / 100.0 * containing_width as f32);
498 }
499 if let Some(n) = t.strip_suffix("in") {
502 return n.trim().parse::<f32>().ok().map(|v| v * 96.0);
503 }
504 if let Some(n) = t.strip_suffix("cm") {
505 return n.trim().parse::<f32>().ok().map(|v| v * 96.0 / 2.54);
506 }
507 if let Some(n) = t.strip_suffix("mm") {
508 return n.trim().parse::<f32>().ok().map(|v| v * 96.0 / 25.4);
509 }
510 if let Some(n) = t.strip_suffix("pt") {
511 return n.trim().parse::<f32>().ok().map(|v| v * 96.0 / 72.0);
512 }
513 if let Some(n) = t.strip_suffix("pc") {
514 return n.trim().parse::<f32>().ok().map(|v| v * 16.0);
515 }
516 if let Some(n) = t.strip_suffix('q') {
517 return n.trim().parse::<f32>().ok().map(|v| v * 96.0 / 101.6);
518 }
519 t.parse::<f32>().ok()
520}
521
522pub fn eval_css_math(expr: &str, containing_width: i32) -> Option<i32> {
526 let t = expr.trim();
527 if t.starts_with("calc(") {
528 return eval_calc(t, containing_width);
529 }
530 if let Some(inner) = t.strip_prefix("min(").and_then(|s| s.strip_suffix(')')) {
531 let vals: alloc::vec::Vec<f32> = split_top_level_commas(inner)
532 .iter()
533 .filter_map(|a| eval_length_token(a, containing_width))
534 .collect();
535 return vals
536 .into_iter()
537 .fold(None, |acc, v| Some(acc.map_or(v, |m: f32| m.min(v))))
538 .map(|v| v as i32);
539 }
540 if let Some(inner) = t.strip_prefix("max(").and_then(|s| s.strip_suffix(')')) {
541 let vals: alloc::vec::Vec<f32> = split_top_level_commas(inner)
542 .iter()
543 .filter_map(|a| eval_length_token(a, containing_width))
544 .collect();
545 return vals
546 .into_iter()
547 .fold(None, |acc, v| Some(acc.map_or(v, |m: f32| m.max(v))))
548 .map(|v| v as i32);
549 }
550 if let Some(inner) = t.strip_prefix("clamp(").and_then(|s| s.strip_suffix(')')) {
551 let parts = split_top_level_commas(inner);
552 if parts.len() != 3 {
553 return None;
554 }
555 let min_v = eval_length_token(parts[0], containing_width)?;
556 let pref_v = eval_length_token(parts[1], containing_width)?;
557 let max_v = eval_length_token(parts[2], containing_width)?;
558 let effective_max = max_v.max(min_v);
561 return Some(pref_v.clamp(min_v, effective_max) as i32);
562 }
563 None
564}
565
566