Fix ordinary inline used sizing
This commit is contained in:
1 parent
42d3c1191b
commit
61d61feb50
6 files changed
+335
-12
No files matched your search
@@ -894,6 +894,7 @@ fn resolve_atomic_percentage_heights(
|
||||
.is_some_and(|node| node.parent == Some(parent_id));
|
||||
let child_percent = styles.get(&dom_id).and_then(|style| {
|
||||
(style.size_expressions[1].is_none()
|
||||
&& !style.ignores_used_box_sizes()
|
||||
&& !matches!(style.position, Some(taffy::Position::Absolute)))
|
||||
.then_some(style.height)
|
||||
.and_then(|height| match height {
|
||||
@@ -1381,6 +1382,7 @@ fn cascade_walk(
|
||||
.filter(|value| value.is_finite() && *value >= 0.0);
|
||||
}
|
||||
let mut style = crate::style::ua_style(elem.local.as_ref());
|
||||
style.is_replaced_box = crate::inline::is_replaced(elem.local.as_ref());
|
||||
style.color_scheme_dark = inherited_color_scheme_dark;
|
||||
if matches!(elem.local.as_ref(), "td" | "th") {
|
||||
if let Some(padding) = inherited_cell_padding {
|
||||
@@ -1480,7 +1482,8 @@ fn cascade_walk(
|
||||
this_props = std::rc::Rc::new(m);
|
||||
}
|
||||
custom_properties.insert(id, this_props.clone());
|
||||
let (before_pseudo, after_pseudo) =
|
||||
style.is_replaced_box |= style.content_image.is_some();
|
||||
let (mut before_pseudo, mut after_pseudo) =
|
||||
if let Some(evaluator) = container_evaluator.as_deref_mut() {
|
||||
sheet.pseudo_styles_with_container_queries(
|
||||
tree,
|
||||
@@ -1493,6 +1496,12 @@ fn cascade_walk(
|
||||
} else {
|
||||
sheet.pseudo_styles(tree, matcher, id, &this_props, &style)
|
||||
};
|
||||
for pseudo in [&mut before_pseudo, &mut after_pseudo]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
pseudo.is_replaced_box = pseudo.content_image.is_some();
|
||||
}
|
||||
style.before_content = before_pseudo
|
||||
.as_ref()
|
||||
.filter(|pseudo| pseudo.position != Some(taffy::Position::Absolute))
|
||||
@@ -2073,6 +2082,7 @@ fn layout_dom_once(
|
||||
opacity_product: f32,
|
||||
list_style: crate::ListStyle,
|
||||
line_height: crate::LineHeight,
|
||||
white_space: crate::WhiteSpace,
|
||||
text_wrap_style: crate::TextWrapStyle,
|
||||
text_transform: crate::TextTransform,
|
||||
italic: bool,
|
||||
@@ -2121,6 +2131,7 @@ fn layout_dom_once(
|
||||
// CSS initial value of list-style-type.
|
||||
list_style: crate::ListStyle::Disc,
|
||||
line_height: crate::LineHeight::Normal,
|
||||
white_space: crate::WhiteSpace::Normal,
|
||||
text_wrap_style: crate::TextWrapStyle::Auto,
|
||||
text_transform: crate::TextTransform::None,
|
||||
italic: false,
|
||||
@@ -2501,6 +2512,7 @@ fn layout_dom_once(
|
||||
inh.visibility_hidden || inh.opacity_product <= 0.0;
|
||||
match style.list_style { Some(v) => inh.list_style = v, None => style.list_style = Some(inh.list_style) }
|
||||
match style.line_height { Some(v) => inh.line_height = v, None => style.line_height = Some(inh.line_height) }
|
||||
match style.white_space { Some(v) => inh.white_space = v, None => style.white_space = Some(inh.white_space) }
|
||||
match style.text_wrap_style { Some(v) => inh.text_wrap_style = v, None => style.text_wrap_style = Some(inh.text_wrap_style) }
|
||||
match style.text_transform { Some(v) => inh.text_transform = v, None => style.text_transform = Some(inh.text_transform) }
|
||||
match style.font_style_italic { Some(v) => inh.italic = v, None => style.font_style_italic = Some(inh.italic) }
|
||||
@@ -2621,6 +2633,7 @@ fn layout_dom_once(
|
||||
let host_optical_sizing = style.font_optical_sizing;
|
||||
let host_variation_settings = style.font_variation_settings.clone();
|
||||
let host_line_height = style.line_height;
|
||||
let host_white_space = style.white_space;
|
||||
let host_text_wrap_style = style.text_wrap_style;
|
||||
let host_transform = style.text_transform;
|
||||
let host_italic = style.font_style_italic;
|
||||
@@ -2835,6 +2848,9 @@ fn layout_dom_once(
|
||||
} else if pseudo.line_height.is_none() {
|
||||
pseudo.line_height = host_line_height;
|
||||
}
|
||||
if pseudo.white_space.is_none() {
|
||||
pseudo.white_space = host_white_space;
|
||||
}
|
||||
if pseudo.text_wrap_style.is_none() {
|
||||
pseudo.text_wrap_style = host_text_wrap_style;
|
||||
}
|
||||
@@ -3210,6 +3226,7 @@ fn layout_dom_once(
|
||||
crate::blockify_outer_display(style);
|
||||
}
|
||||
}
|
||||
blockify_generated_pseudos(&mut styles);
|
||||
|
||||
// The initial containing block is modelled by the root taffy node's
|
||||
// definite viewport height (set at build), which is what `html,body{
|
||||
@@ -5671,6 +5688,30 @@ fn blockify_layout_children(
|
||||
}
|
||||
}
|
||||
|
||||
/// Generated `::before`/`::after` boxes participate in the same display
|
||||
/// fixups as real elements. They are stored inside their host style rather
|
||||
/// than in the DOM map, so the normal root/item/float/positioned loops above
|
||||
/// cannot reach them.
|
||||
fn blockify_generated_pseudos(styles: &mut HashMap<NodeId, crate::LayoutStyle>) {
|
||||
for host in styles.values_mut() {
|
||||
let host_is_item_container = matches!(
|
||||
host.display,
|
||||
crate::Display::Flex | crate::Display::Grid
|
||||
) && !host.internal_flex_container;
|
||||
for pseudo in [host.before_pseudo.as_mut(), host.after_pseudo.as_mut()]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
if host_is_item_container
|
||||
|| matches!(pseudo.position, Some(taffy::Position::Absolute))
|
||||
|| pseudo.float.is_some()
|
||||
{
|
||||
crate::blockify_outer_display(pseudo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build whichever of an element or a text node `id` is, returning every
|
||||
/// taffy node it produced (usually one, but a text node fans out to one leaf
|
||||
/// per word; see `build_text_words`). Callers collecting a container's
|
||||
@@ -5914,8 +5955,6 @@ fn is_flattenable_inline(tree: &DomTree, id: NodeId, styles: &HashMap<NodeId, cr
|
||||
&& style.background_image.is_none()
|
||||
&& style.mask_image.is_none()
|
||||
&& style.border == crate::Edges::default()
|
||||
&& style.width == crate::Dimension::Auto
|
||||
&& style.height == crate::Dimension::Auto
|
||||
&& style.position.is_none()
|
||||
&& !style.overflow_hidden
|
||||
&& style.float.is_none()
|
||||
@@ -6085,12 +6124,6 @@ fn pseudo_requires_generated_box(style: &crate::LayoutStyle, content: Option<&st
|
||||
|| style.display != crate::Display::Inline
|
||||
|| style.is_inline_block
|
||||
|| style.position.is_some()
|
||||
|| style.width != crate::Dimension::Auto
|
||||
|| style.height != crate::Dimension::Auto
|
||||
|| style.min_width != crate::Dimension::Auto
|
||||
|| style.min_height != crate::Dimension::Auto
|
||||
|| style.max_width != crate::Dimension::Auto
|
||||
|| style.max_height != crate::Dimension::Auto
|
||||
|| style.margin != crate::Edges::default()
|
||||
|| style.margin_auto.iter().any(|value| *value)
|
||||
|| style.padding != crate::Edges::default()
|
||||
@@ -6374,7 +6407,7 @@ where
|
||||
.iter()
|
||||
.filter_map(|(&node, &dom)| {
|
||||
let style = styles.get(&dom)?;
|
||||
if !style.width_fit_content {
|
||||
if !style.width_fit_content || style.ignores_used_box_sizes() {
|
||||
return None;
|
||||
}
|
||||
let layout = taffy_tree.layout(node).ok()?;
|
||||
@@ -7017,6 +7050,7 @@ fn defer_cyclic_flex_inline_sizes(
|
||||
) -> Vec<DeferredCyclicInlineSize> {
|
||||
let candidates: Vec<(NodeId, usize, String)> = styles
|
||||
.iter()
|
||||
.filter(|(_, style)| !style.ignores_used_box_sizes())
|
||||
.flat_map(|(&id, style)| {
|
||||
[0usize, 2, 4].into_iter().filter_map(move |slot| {
|
||||
style.size_expressions[slot]
|
||||
@@ -10833,6 +10867,124 @@ mod tests {
|
||||
assert_eq!(width("border-cell"), 100.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_space_inherits_into_a_block_descendant_inline_context() {
|
||||
let tree = parse_html(
|
||||
r#"<style>
|
||||
html, body, pre { margin:0 }
|
||||
pre { width:120px; white-space:pre; font-size:16px; line-height:20px }
|
||||
code { display:flex; flex-direction:column }
|
||||
.line { display:block }
|
||||
#normal { white-space:normal }
|
||||
#inherited::before { content:""; display:none }
|
||||
</style>
|
||||
<pre><code>
|
||||
<span id="inherited" class="line">alpha beta gamma delta</span>
|
||||
<span id="normal" class="line">alpha beta gamma delta</span>
|
||||
</code></pre>"#,
|
||||
);
|
||||
let laid = layout_dom(&tree, (400.0, 200.0));
|
||||
let inherited = tree.get_element_by_id("inherited").unwrap();
|
||||
let normal = tree.get_element_by_id("normal").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
laid.styles[&inherited].white_space,
|
||||
Some(crate::WhiteSpace::Pre),
|
||||
"a block descendant must inherit the preformatted wrapping mode"
|
||||
);
|
||||
assert_eq!(
|
||||
laid.styles[&normal].white_space,
|
||||
Some(crate::WhiteSpace::Normal),
|
||||
"an explicit descendant value must override inherited pre"
|
||||
);
|
||||
assert_eq!(
|
||||
laid.styles[&inherited]
|
||||
.before_pseudo
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.white_space,
|
||||
Some(crate::WhiteSpace::Pre),
|
||||
"a pseudo-element must inherit the originating element's computed wrapping mode"
|
||||
);
|
||||
assert_eq!(
|
||||
laid.rects[&inherited].height, 20.0,
|
||||
"the inherited pre line must overflow horizontally without wrapping"
|
||||
);
|
||||
assert!(
|
||||
laid.rects[&normal].height > 20.0,
|
||||
"the explicit normal line should still wrap at the containing width"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated_inline_boxes_apply_sizes_only_after_display_blockification() {
|
||||
let tree = parse_html(
|
||||
r#"<style>
|
||||
html,body { margin:0 }
|
||||
#flex { display:flex }
|
||||
#flex::before {
|
||||
content:""; display:inline;
|
||||
width:100px; height:70px;
|
||||
min-width:250px; min-height:120px;
|
||||
max-width:50px; max-height:40px;
|
||||
background:red
|
||||
}
|
||||
#float::before {
|
||||
content:""; display:inline; float:left;
|
||||
width:80px; height:40px; background:blue
|
||||
}
|
||||
#relative::before {
|
||||
content:""; display:inline; position:relative;
|
||||
width:80px; height:40px; background:green
|
||||
}
|
||||
</style>
|
||||
<div id="flex"></div>
|
||||
<div id="float"></div>
|
||||
<div id="relative"></div>"#,
|
||||
);
|
||||
let laid = layout_dom(&tree, (500.0, 300.0));
|
||||
let flex = tree.get_element_by_id("flex").unwrap();
|
||||
let float = tree.get_element_by_id("float").unwrap();
|
||||
let relative = tree.get_element_by_id("relative").unwrap();
|
||||
|
||||
let pseudo = |host| {
|
||||
laid.generated_boxes
|
||||
.iter()
|
||||
.find(|generated| {
|
||||
generated.host == host && generated.kind == GeneratedBoxKind::Before
|
||||
})
|
||||
.unwrap()
|
||||
.rect
|
||||
};
|
||||
assert_eq!(
|
||||
laid.styles[&flex].before_pseudo.as_ref().unwrap().display,
|
||||
crate::Display::Block,
|
||||
"a generated flex item must be blockified"
|
||||
);
|
||||
assert_eq!(
|
||||
laid.styles[&float].before_pseudo.as_ref().unwrap().display,
|
||||
crate::Display::Block,
|
||||
"a floated generated box must be blockified"
|
||||
);
|
||||
assert_eq!(
|
||||
laid.styles[&relative]
|
||||
.before_pseudo
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.display,
|
||||
crate::Display::Inline,
|
||||
"relative positioning does not make an ordinary inline atomic"
|
||||
);
|
||||
assert_eq!(pseudo(flex).width, 250.0);
|
||||
assert_eq!(pseudo(flex).height, 120.0);
|
||||
assert_eq!(pseudo(float).width, 80.0);
|
||||
assert_eq!(pseudo(float).height, 40.0);
|
||||
assert!(
|
||||
pseudo(relative).width < 80.0 && pseudo(relative).height < 40.0,
|
||||
"an ordinary inline pseudo must ignore authored box sizes"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn positioned_pseudo_inherits_the_hosts_computed_font_metrics() {
|
||||
let tree = parse_html(
|
||||
|
||||
@@ -1812,7 +1812,7 @@ fn is_pure_text_ifc(
|
||||
/// contains one is never a pure-text IFC (it keeps the general build path,
|
||||
/// where the element gets a real taffy box). This is by tag, not display, so a
|
||||
/// stylesheet setting `img{display:inline}` cannot trick us into folding it.
|
||||
fn is_replaced(local: &str) -> bool {
|
||||
pub(crate) fn is_replaced(local: &str) -> bool {
|
||||
matches!(
|
||||
local,
|
||||
"img" | "svg" | "canvas" | "video" | "audio" | "iframe" | "embed"
|
||||
|
||||
@@ -512,6 +512,14 @@ pub struct LayoutStyle {
|
||||
/// (`<img>` or its selected `<picture><source>`), rather than authored
|
||||
/// CSS. A decoded image's natural ratio replaces this provisional ratio.
|
||||
pub aspect_ratio_is_mapped: bool,
|
||||
/// Whether this element generates a replaced box.
|
||||
///
|
||||
/// Replaced elements with `display:inline` are atomic and therefore keep
|
||||
/// authored width/height/min/max sizing. Ordinary inline boxes compute
|
||||
/// those properties but ignore them for used layout. This provenance
|
||||
/// cannot be inferred from intrinsic dimensions because an unloaded or
|
||||
/// broken image remains replaced.
|
||||
pub(crate) is_replaced_box: bool,
|
||||
pub margin: Edges,
|
||||
/// Which margin sides are `auto` (top, right, bottom, left). `margin: 0
|
||||
/// auto` / `margin-inline: auto` centering needs a real Auto margin, which
|
||||
@@ -994,6 +1002,13 @@ pub(crate) const CB_TRIGGER_CONTENT_VISIBILITY: u16 = 1 << 6;
|
||||
pub(crate) const CB_TRIGGER_TRANSLATE: u16 = 1 << 7;
|
||||
|
||||
impl LayoutStyle {
|
||||
/// Whether CSS box sizes compute normally but do not apply to this box's
|
||||
/// used geometry. The display value here is post-blockification, so roots,
|
||||
/// flex/grid items, floats, and out-of-flow boxes retain applicable sizes.
|
||||
pub(crate) fn ignores_used_box_sizes(&self) -> bool {
|
||||
self.display == Display::Inline && !self.is_inline_block && !self.is_replaced_box
|
||||
}
|
||||
|
||||
pub(crate) fn establishes_positioning_containing_block(&self) -> bool {
|
||||
self.containing_block_triggers != 0
|
||||
}
|
||||
@@ -1347,6 +1362,21 @@ pub(crate) fn to_taffy_style(style: &LayoutStyle) -> Style {
|
||||
s.aspect_ratio = Some(ar);
|
||||
}
|
||||
}
|
||||
if style.ignores_used_box_sizes() {
|
||||
s.size = taffy::Size {
|
||||
width: taffy::Dimension::auto(),
|
||||
height: taffy::Dimension::auto(),
|
||||
};
|
||||
s.min_size = taffy::Size {
|
||||
width: taffy::Dimension::auto(),
|
||||
height: taffy::Dimension::auto(),
|
||||
};
|
||||
s.max_size = taffy::Size {
|
||||
width: taffy::Dimension::auto(),
|
||||
height: taffy::Dimension::auto(),
|
||||
};
|
||||
s.aspect_ratio = None;
|
||||
}
|
||||
if style.display != Display::Block {
|
||||
if let Some(ai) = style.align_items {
|
||||
s.align_items = Some(ai);
|
||||
|
||||
@@ -391,6 +391,9 @@ impl PreparedRender {
|
||||
) -> Option<(f32, f32)> {
|
||||
let rect = self.layout.rects.get(&id)?;
|
||||
let style = self.layout.styles.get(&id)?;
|
||||
if style.ignores_used_box_sizes() {
|
||||
return Some((0.0, 0.0));
|
||||
}
|
||||
Some((
|
||||
(rect.width - style.border.left - style.border.right).max(0.0),
|
||||
(rect.height - style.border.top - style.border.bottom).max(0.0),
|
||||
@@ -482,7 +485,10 @@ impl PreparedRender {
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(rect) = rect {
|
||||
if style.ignores_used_box_sizes() {
|
||||
out.insert("width", dimension_css(style.width, "auto"));
|
||||
out.insert("height", dimension_css(style.height, "auto"));
|
||||
} else if let Some(rect) = rect {
|
||||
let horizontal_non_content =
|
||||
style.border.left + style.border.right + style.padding.left + style.padding.right;
|
||||
let vertical_non_content =
|
||||
|
||||
@@ -1272,6 +1272,49 @@ fn text_alignment_does_not_shrink_block_children_or_wrap_inline_block_rows() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_replaced_inline_ignores_used_width_height_and_min_max_sizes() {
|
||||
let tree = parse_html(
|
||||
r#"<style>
|
||||
html,body,p{margin:0}
|
||||
#host{width:300px;font-size:16px;line-height:20px}
|
||||
#token{
|
||||
position:relative;
|
||||
width:100%;height:100px;
|
||||
min-width:100%;min-height:100px;
|
||||
max-width:100%;max-height:100px;
|
||||
padding:0 5px;
|
||||
background:red
|
||||
}
|
||||
#after{position:relative;background:blue}
|
||||
</style>
|
||||
<p id="host">A <code id="token">token</code> <span id="after">after</span></p>"#,
|
||||
);
|
||||
let laid = layout_dom(&tree, (400.0, 200.0));
|
||||
let token_id = tree.get_element_by_id("token").unwrap();
|
||||
let after_id = tree.get_element_by_id("after").unwrap();
|
||||
let token = laid.rects[&token_id];
|
||||
let after = laid.rects[&after_id];
|
||||
|
||||
assert_eq!(
|
||||
laid.styles[&token_id].width,
|
||||
obscura_render::Dimension::Percent(1.0),
|
||||
"computed style must retain the authored percentage"
|
||||
);
|
||||
assert!(
|
||||
token.width > 20.0 && token.width < 100.0,
|
||||
"a non-replaced inline's used width must hug its text and padding: {token:?}"
|
||||
);
|
||||
assert!(
|
||||
token.height < 40.0,
|
||||
"height/min-height/max-height do not apply to a non-replaced inline: {token:?}"
|
||||
);
|
||||
assert!(
|
||||
after.x >= token.x + token.width - 0.01 && (after.y - token.y).abs() < 0.01,
|
||||
"the following inline must remain on the same line: {token:?} {after:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inline_svg_derives_auto_height_from_view_box() {
|
||||
let tree = parse_html(
|
||||
|
||||
Reference in new issue
Block a user