diff --git a/crates/obscura-render/src/dom.rs b/crates/obscura-render/src/dom.rs
index 065efd1..e8bad25 100644
--- a/crates/obscura-render/src/dom.rs
+++ b/crates/obscura-render/src/dom.rs
@@ -9435,6 +9435,38 @@ mod tests {
assert_eq!(pseudo.line_height, host.line_height);
}
+ #[test]
+ fn positioned_pseudo_does_not_wrap_shrink_to_fit_host_text() {
+ let tree = parse_html(
+ r#"
+ "#,
+ );
+ let laid = layout_dom(&tree, (500.0, 300.0));
+ let host = tree.get_element_by_id("host").unwrap();
+ let rect = laid.rects[&host];
+
+ assert!(
+ (120.0..=130.0).contains(&rect.width),
+ "max-content width must include the shaped inter-word advance: {rect:?}"
+ );
+ assert_eq!(
+ rect.height, 40.0,
+ "positioned generated content must not make the host label wrap"
+ );
+ }
+
#[test]
fn variable_font_properties_inherit_and_reset_through_elements_and_pseudos() {
let tree = parse_html(
diff --git a/crates/obscura-render/src/inline.rs b/crates/obscura-render/src/inline.rs
index e83fbc8..1f84bcb 100644
--- a/crates/obscura-render/src/inline.rs
+++ b/crates/obscura-render/src/inline.rs
@@ -1568,7 +1568,20 @@ fn is_pure_text_ifc(
let Some(style) = styles.get(&id) else {
return false;
};
- if style.before_pseudo.is_some() || style.after_pseudo.is_some() {
+ // Out-of-flow generated boxes neither contribute to nor interrupt the
+ // host's inline formatting context. Keeping a positioned decorative
+ // pseudo must not demote an otherwise text-only shrink-to-fit control to
+ // approximate per-word flex leaves: that path can disagree with shaped
+ // space advances and wrap text at its own max-content width.
+ let has_in_flow_pseudo = |pseudo: Option<&LayoutStyle>| {
+ pseudo.is_some_and(|pseudo| {
+ pseudo.display != Display::None
+ && !matches!(pseudo.position, Some(taffy::Position::Absolute))
+ })
+ };
+ if has_in_flow_pseudo(style.before_pseudo.as_deref())
+ || has_in_flow_pseudo(style.after_pseudo.as_deref())
+ {
return false;
}
// Only containers that lay their children out in normal flow (block, or