Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 31 additions & 9 deletions crates/rustmotion-core/src/engine/layout_pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use taffy::prelude as tf;
use taffy::TaffyTree;

use crate::css::taffy_bridge::{to_taffy_style, ConversionContext};
use crate::css::units::LengthContext;
use crate::engine::box_tree::{BoxNode, IntrinsicMeasure, NodeId};

/// Resolved geometry for a single node, in absolute viewport coordinates.
Expand Down Expand Up @@ -85,7 +86,7 @@ pub fn run_layout(root: &BoxNode, viewport: (f32, f32), ctx: &ConversionContext)
let mut node_map: HashMap<NodeId, tf::NodeId> = HashMap::new();

// Build the taffy tree top-down.
let root_tf = build(&mut tree, &mut node_map, root, ctx);
let root_tf = build(&mut tree, &mut node_map, root, ctx, ctx.length.font_size);

let viewport_size = tf::Size {
width: tf::AvailableSpace::Definite(viewport.0),
Expand Down Expand Up @@ -119,33 +120,54 @@ pub fn run_layout(root: &BoxNode, viewport: (f32, f32), ctx: &ConversionContext)
LayoutResult { layouts }
}

/// Build one taffy node and, recursively, its subtree.
///
/// `inherited_font_size` is the already-resolved (px) font-size of `node`'s
/// parent: CSS resolves `em` on every layout property against the
/// element's *own* computed font-size, and font-size itself inherits down
/// the tree unless overridden. `to_taffy_style` only ever sees the single
/// `ConversionContext` handed to it, so its `em` resolution is only as
/// correct as the per-node context built here — a call site building one
/// shared `ConversionContext` for the whole tree (as every production
/// caller of `to_taffy_style` still does directly) resolves every node's
/// `em` against that one context's `font_size` instead.
fn build(
tree: &mut TaffyTree<NodeData>,
map: &mut HashMap<NodeId, tf::NodeId>,
node: &BoxNode,
ctx: &ConversionContext,
inherited_font_size: f32,
) -> tf::NodeId {
let style = to_taffy_style(&node.css, ctx);
let parent_font_ctx = LengthContext {
font_size: inherited_font_size,
..ctx.length
};
let own_font_size = node
.css
.font_size_px_ctx(&parent_font_ctx, inherited_font_size);
let node_ctx = ConversionContext {
length: LengthContext {
font_size: own_font_size,
..ctx.length
},
};

let style = to_taffy_style(&node.css, &node_ctx);
let data = NodeData {
box_id: node.id,
intrinsic: node.intrinsic.clone(),
};
let tf_id = if node.intrinsic.is_some() {
// Leaf with intrinsic measurement.
tree.new_leaf_with_context(style, data)
.expect("taffy new_leaf")
} else if node.children.is_empty() {
let tf_id = if node.intrinsic.is_some() || node.children.is_empty() {
tree.new_leaf_with_context(style, data)
.expect("taffy new_leaf")
} else {
let mut child_ids = Vec::with_capacity(node.children.len());
for c in &node.children {
child_ids.push(build(tree, map, c, ctx));
child_ids.push(build(tree, map, c, ctx, own_font_size));
}
let id = tree
.new_with_children(style, &child_ids)
.expect("taffy new_with_children");
// We still want context on internal nodes (for box_id mapping).
tree.set_node_context(id, Some(data)).ok();
id
};
Expand Down
47 changes: 47 additions & 0 deletions crates/rustmotion-core/tests/audit_ws_j.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! Regression tests for the workstream J (layout pass & CSS unit resolution)
//! audit findings on unit resolution and intrinsic measurement.

use rustmotion_core::css::style::{CssStyle, Edges, Size as CSize};
use rustmotion_core::css::taffy_bridge::ConversionContext;
use rustmotion_core::css::units::{Length, LengthPercentage as CLP};
use rustmotion_core::engine::box_tree::BoxNode;
use rustmotion_core::engine::layout_pass::run_layout;

// ---- `em` on layout properties must resolve against the element's
// own (inherited) font-size, not a constant 16px ----

/// A card sized by its parent's explicit `font-size` (48px) inherits that
/// font-size down to its own layout resolution even though it never sets
/// `font-size` itself. Its `padding: "1em"` must therefore resolve to 48px
/// (`1 * inherited_font_size`), not the pre-fix constant 16px.
#[test]
fn em_padding_resolves_against_inherited_font_size_not_a_constant_16px() {
let mut root = BoxNode::container(
CssStyle {
font_size: Some(Length::Px(48.0)),
width: Some(CSize::Length(CLP::Px(400.0))),
height: Some(CSize::Length(CLP::Px(400.0))),
..Default::default()
},
vec![BoxNode::container(
CssStyle {
padding: Some(Edges::Uniform(CLP::String("1em".into()))),
width: Some(CSize::Length(CLP::Px(200.0))),
height: Some(CSize::Length(CLP::Px(200.0))),
..Default::default()
},
vec![],
)],
);
root.assign_ids(1);

let res = run_layout(&root, (400.0, 400.0), &ConversionContext::default());
let child = res.get(2).expect("child laid out");
let (content_x, _, content_w, _) = child.content_box();

assert_eq!(
content_x, 48.0,
"1em padding must resolve against the inherited 48px font-size"
);
assert_eq!(content_w, 200.0 - 2.0 * 48.0);
}
Loading