fix(layout): preserve double precision transform matrices

This commit is contained in:
ldm0
2026-09-09 20:38:30 +08:00
parent 03d4d3f013
commit 78d80d5279
3 changed files with 76 additions and 7 deletions
+5 -7
View File
@@ -2375,19 +2375,17 @@ fn resolve_stylo_2d_transform(
} else {
match box_styles
.transform
.to_transform_3d_matrix(Some(&reference_box))
// Keep the computed matrix in double precision until mapping the
// final points. Casting coefficients to float here loses geometry
// precision even if they are widened again for LayoutTransform2D.
.to_transform_3d_matrix_f64(Some(&reference_box))
{
Ok((_matrix, true)) => {
has_unsupported_3d = true;
None
}
Ok((matrix, false)) => Some(LayoutTransform2D::new([
f64::from(matrix.m11),
f64::from(matrix.m12),
f64::from(matrix.m21),
f64::from(matrix.m22),
f64::from(matrix.m41),
f64::from(matrix.m42),
matrix.m11, matrix.m12, matrix.m21, matrix.m22, matrix.m41, matrix.m42,
])),
Err(_) => {
has_unsupported_3d = true;
@@ -1,5 +1,7 @@
use super::*;
mod transform_precision;
use base64::Engine as _;
use super::super::main_document_lifecycle_completion::execute_main_document_lifecycle_on_owner_local_task;
@@ -0,0 +1,69 @@
use super::*;
#[tokio::test(flavor = "current_thread")]
async fn rotated_client_rects_retain_matrix_precision_until_point_mapping() {
run_page_vm_async_test(async move {
let loader =
crate::network::ResourceRequestClient::new(&FetchConfig::default()).expect("loader");
let mut page_vm = test_page_vm_with_loader_and_document_url(
&loader,
Vec::new(),
Url::parse("https://example.com/transform-precision.html")?,
);
page_vm.vm_mut().eval(
r#"
document.head.innerHTML = `<style>
html,body{margin:0}
div{position:absolute;left:0;top:0;width:100px;height:100px}
</style>`;
for (const angle of [0,15,30,45,135,-45]) {
for (const property of ['transform', 'rotate']) {
const element = document.createElement('div');
element.id = property + angle;
element.style[property] = property === 'transform' ? `rotate(${angle}deg)` : `${angle}deg`;
document.body.appendChild(element);
}
}
'installed'
"#,
)?;
page_vm.vm_mut().sync_live_document_style_sources();
page_vm
.vm_mut()
.screenshot_layout_snapshot(moli_layout::PaintViewport::new(200, 200, 1.0))?
.expect("transform fixture must retain a layout root");
// Chromium maps points with a double-precision transform, then rounds
// the resulting geometry to float. Rounding the matrix first changes
// x/y even when the final bounding width and height happen to match.
// Non-right angles exercise matrix coefficient precision. Exact
// quarter-turn snapping is a separate transform contract.
assert_eq!(
page_vm.vm_mut().eval(
r#"(() => {
const r = document.getElementById('transform45').getClientRects()[0];
return [r.x, r.y, r.width, r.height, r.right, r.bottom].join('|');
})()"#,
)?,
"-20.710678100585938|-20.710678100585938|141.42135620117188|141.42135620117188|120.71067810058594|120.71067810058594",
);
assert_eq!(
page_vm.vm_mut().eval(
r#"(() => {
const geometry = id => {
const r = document.getElementById(id).getBoundingClientRect();
return [r.x, r.y, r.width, r.height].join('|');
};
return [0,15,30,45,135,-45].filter(angle =>
geometry('transform' + angle) !== geometry('rotate' + angle)
).join(',');
})()"#,
)?,
"",
"transform:rotate() and the individual rotate property must map identical points",
);
Ok::<_, anyhow::Error>(())
})
.await
.expect("transform precision fixture should run");
}