diff --git a/crates/obscura-render/src/style.rs b/crates/obscura-render/src/style.rs index 9a4992e..39f23fb 100644 --- a/crates/obscura-render/src/style.rs +++ b/crates/obscura-render/src/style.rs @@ -1752,9 +1752,37 @@ fn resolve_contextual(value: &str, context: &LengthContext) -> Option { return Some(preferred.min(high).max(low)); } } + if let Some(rest) = value.strip_prefix("round(") { + let end = find_matching_paren(rest)?; + let args = split_top_level(&rest[..end], ','); + let (strategy, value_index) = match args.first()?.trim() { + "nearest" | "up" | "down" | "to-zero" => (args[0].trim(), 1), + _ => ("nearest", 0), + }; + let resolved = eval_contextual_calc(args.get(value_index)?.trim(), context)?; + let step = match args.get(value_index + 1) { + Some(step) => eval_contextual_calc(step.trim(), context)?, + None => 1.0, + }; + return round_css_value(resolved, step, strategy); + } contextual_atom(value, context) } +fn round_css_value(value: f32, step: f32, strategy: &str) -> Option { + if !value.is_finite() || !step.is_finite() || step == 0.0 { + return None; + } + let quotient = value / step.abs(); + let rounded = match strategy { + "up" => quotient.ceil(), + "down" => quotient.floor(), + "to-zero" => quotient.trunc(), + _ => quotient.round(), + }; + Some(rounded * step.abs()) +} + fn contextual_atom(value: &str, context: &LengthContext) -> Option { let lower = value.trim().to_ascii_lowercase(); let parse = |number: &str| number.trim().parse::().ok(); @@ -1915,6 +1943,20 @@ fn resolve_length(value: &str) -> Option { return Some(mid.min(hi).max(lo)); } } + if let Some(rest) = v.strip_prefix("round(") { + let end = find_matching_paren(rest)?; + let args = split_top_level(&rest[..end], ','); + let (strategy, value_index) = match args.first()?.trim() { + "nearest" | "up" | "down" | "to-zero" => (args[0].trim(), 1), + _ => ("nearest", 0), + }; + let resolved = eval_calc(args.get(value_index)?.trim())?; + let step = match args.get(value_index + 1) { + Some(step) => eval_calc(step.trim())?, + None => 1.0, + }; + return round_css_value(resolved, step, strategy); + } if v.contains('(') { return None; // an unhandled function (env(), ...): no safe fallback } @@ -2965,6 +3007,17 @@ mod tests { ), Some(122.0) ); + assert_eq!( + resolve_contextual_length( + "calc(round(247px * 1, 10px))", + context.0, + context.1, + context.2, + context.3, + context.4, + ), + Some(250.0) + ); } #[test]