Files
moli/moli-css-parse/src/root_margin.rs
T
ldm0 e78f5a33b2 feat(layout): update Taffy and Stylo forks with current Blitz adapters
Move Taffy to 0.15 and Stylo to 0.22 using fixed fork revisions, with the
matching stylo_taffy adapter. Upgrade cssparser and the production HTML/XML
parsers while preserving the vendored XML behavior.

Adapt sizing inputs, baselines, grid positions, overflow and out-of-flow
ownership. Preserve font-stretch CSSOM compatibility and intrinsic sizing
through nested atomic inline probes.

Validation: root cargo fmt --all; strict workspace/all-target/all-feature
Clippy; Nextest 20280 passed, 18 skipped; Release build; five browser
regression rounds; RTL and nested intrinsic Chromium geometry controls.
Full WPT Cross is recorded separately with its Cases refresh.

Original-commit: 281a920d89
2026-10-21 17:55:02 +08:00

131 lines
4.2 KiB
Rust

use cssparser::{Parser, Token};
pub fn normalize_root_margin(value: &str) -> Option<String> {
let input = value;
let mut parser = Parser::new(input);
let raw_parts = parser.parse_entirely(parse_root_margin_components).ok()?;
let parts: Vec<&str> = match raw_parts.as_slice() {
[] => return Some("0px 0px 0px 0px".to_owned()),
[a] => vec![a.as_str(), a.as_str(), a.as_str(), a.as_str()],
[a, b] => vec![a.as_str(), b.as_str(), a.as_str(), b.as_str()],
[a, b, c] => vec![a.as_str(), b.as_str(), c.as_str(), b.as_str()],
[a, b, c, d] => vec![a.as_str(), b.as_str(), c.as_str(), d.as_str()],
_ => return None,
};
Some(parts.join(" "))
}
pub fn root_margin_components(value: &str, root_width: f64) -> [f64; 4] {
let mut result = [0.0; 4];
for (index, part) in value.split_ascii_whitespace().take(4).enumerate() {
result[index] = if let Some(number) = part.strip_suffix("px") {
number.parse::<f64>().unwrap_or(0.0)
} else if let Some(number) = part.strip_suffix('%') {
number
.parse::<f64>()
.map(|percentage| (percentage / 100.0) * root_width)
.unwrap_or(0.0)
} else {
0.0
};
}
result
}
fn parse_root_margin_components<'i>(
input: &mut Parser<'i>,
) -> Result<Vec<String>, cssparser::ParseError<()>> {
let mut parts = Vec::new();
while !input.is_exhausted() {
if parts.len() == 4 {
return Err(cssparser::ParseError::custom(()));
}
parts.push(parse_root_margin_component(input)?);
}
Ok(parts)
}
fn parse_root_margin_component<'i>(
input: &mut Parser<'i>,
) -> Result<String, cssparser::ParseError<()>> {
match input.next()?.clone() {
Token::Dimension { value, unit, .. } if unit.eq_ignore_ascii_case("px") => {
format_css_number(value)
.map(|number| format!("{number}px"))
.ok_or_else(|| cssparser::ParseError::custom(()))
}
Token::Percentage { unit_value, .. } => format_css_number(unit_value * 100.0)
.map(|number| format!("{number}%"))
.ok_or_else(|| cssparser::ParseError::custom(())),
_ => Err(cssparser::ParseError::custom(())),
}
}
fn format_css_number(value: f32) -> Option<String> {
if !value.is_finite() {
return None;
}
if value == 0.0 {
return Some("0".to_owned());
}
let mut text = value.to_string();
if let Some(dot) = text.find('.') {
while text.ends_with('0') {
text.pop();
}
if text.len() == dot + 1 {
text.pop();
}
}
Some(text)
}
#[cfg(test)]
mod tests {
use super::{normalize_root_margin, root_margin_components};
#[test]
fn parser_uses_cssparser_tokens() {
for value in ["", " \r\n\t\u{c}", "/**/", "/* unclosed", " /**/ \n /**/ "] {
assert_eq!(
normalize_root_margin(value).as_deref(),
Some("0px 0px 0px 0px"),
"{value:?}",
);
}
assert_eq!(
normalize_root_margin("10px /*comment*/ 5% 0px -2.5px").as_deref(),
Some("10px 5% 0px -2.5px")
);
assert_eq!(
normalize_root_margin("\n1PX\t2px").as_deref(),
Some("1px 2px 1px 2px")
);
}
#[test]
fn parser_rejects_non_margin_tokens() {
for value in [
"0", "+0", "-0", "0.0", "0e0", "1e-50", "1px 0", "0% 0 1px", "\u{a0}", "\u{2003}",
"\u{b}", "\u{85}", "\u{2028}", "\u{2029}", "()", "[]", "{}", ")", "]", "}",
] {
assert_eq!(normalize_root_margin(value), None, "{value:?}");
}
assert_eq!(normalize_root_margin("calc(1px)").as_deref(), None);
assert_eq!(normalize_root_margin("1px, 2px").as_deref(), None);
assert_eq!(normalize_root_margin("1em").as_deref(), None);
assert_eq!(
normalize_root_margin("1px 2px 3px 4px 5px").as_deref(),
None
);
}
#[test]
fn components_resolve_percentages_against_root_width() {
assert_eq!(
root_margin_components("10px 5% 0px -2.5px", 200.0),
[10.0, 10.0, 0.0, -2.5]
);
}
}