Match forced line break geometry
This commit is contained in:
1 parent
6fdcd19898
commit
b0b4deeee5
2 files changed
+108
-16
No files matched your search
@@ -6033,7 +6033,12 @@ fn build_flex_grid_children(
|
|||||||
/// wrapping plain text with no inline styling.
|
/// wrapping plain text with no inline styling.
|
||||||
fn is_flattenable_inline(tree: &DomTree, id: NodeId, styles: &HashMap<NodeId, crate::LayoutStyle>) -> bool {
|
fn is_flattenable_inline(tree: &DomTree, id: NodeId, styles: &HashMap<NodeId, crate::LayoutStyle>) -> bool {
|
||||||
let Some(node) = tree.get_node(id) else { return false };
|
let Some(node) = tree.get_node(id) else { return false };
|
||||||
if node.as_element().is_none() {
|
let Some(element) = node.as_element() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
// BR is boxless-looking but semantically contributes a mandatory break.
|
||||||
|
// Flattening it as an empty wrapper deletes the break entirely.
|
||||||
|
if element.local.as_ref() == "br" {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let Some(style) = styles.get(&id) else { return false };
|
let Some(style) = styles.get(&id) else { return false };
|
||||||
@@ -7498,15 +7503,16 @@ fn build(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A forced break that reaches the general box builder sits between block
|
// Outside a foldable inline run, BR is a zero-inline-size line
|
||||||
// or atomic siblings, so it cannot be folded into a shaped inline run.
|
// participant. The mixed-inline fallback adds a separate anonymous
|
||||||
// Give that sentinel the inherited used line-height instead of the UA
|
// full-width breaker after this mapped marker; keeping that control box
|
||||||
// style's zero placeholder. This is the block/flex analogue of BRFrame's
|
// anonymous prevents its layout surrogate from leaking into CSSOM/paint.
|
||||||
// empty-line contribution; break tags inside text and mixed block runs
|
|
||||||
// are handled by the inline collector below.
|
|
||||||
if _name.local.as_ref() == "br" {
|
if _name.local.as_ref() == "br" {
|
||||||
let height = crate::inline::used_line_height(style).max(0.0);
|
let height = crate::inline::used_line_height(style).max(0.0);
|
||||||
|
taffy_style.size.width = taffy::style::Dimension::length(0.0);
|
||||||
taffy_style.size.height = taffy::style::Dimension::length(height);
|
taffy_style.size.height = taffy::style::Dimension::length(height);
|
||||||
|
taffy_style.flex_grow = 0.0;
|
||||||
|
taffy_style.flex_shrink = 0.0;
|
||||||
let leaf = taffy_tree.new_leaf(taffy_style).ok()?;
|
let leaf = taffy_tree.new_leaf(taffy_style).ok()?;
|
||||||
id_map.insert(leaf, id);
|
id_map.insert(leaf, id);
|
||||||
return Some(leaf);
|
return Some(leaf);
|
||||||
@@ -8322,7 +8328,53 @@ fn build_mixed_block(
|
|||||||
before_pending = false;
|
before_pending = false;
|
||||||
}
|
}
|
||||||
for &rc in run {
|
for &rc in run {
|
||||||
atoms.extend(build_any(tree, rc, taffy_tree, id_map, words, engine, ifc_items, styles));
|
let is_forced_break = tree
|
||||||
|
.get_node(rc)
|
||||||
|
.is_some_and(|node| {
|
||||||
|
node.as_element()
|
||||||
|
.is_some_and(|element| element.local.as_ref() == "br")
|
||||||
|
});
|
||||||
|
if is_forced_break {
|
||||||
|
// A BR participates in the current line with zero
|
||||||
|
// inline size, then forces the following content onto
|
||||||
|
// a new line. A single 100%-wide, line-height-tall flex
|
||||||
|
// item creates a phantom third line for A<br>B. Split
|
||||||
|
// those responsibilities: the mapped marker supplies
|
||||||
|
// the current line strut, while an anonymous 100%
|
||||||
|
// breaker may occupy its own zero-height flex line.
|
||||||
|
atoms.extend(build_any(
|
||||||
|
tree,
|
||||||
|
rc,
|
||||||
|
taffy_tree,
|
||||||
|
id_map,
|
||||||
|
words,
|
||||||
|
engine,
|
||||||
|
ifc_items,
|
||||||
|
styles,
|
||||||
|
));
|
||||||
|
let breaker_style = taffy::Style {
|
||||||
|
flex_grow: 0.0,
|
||||||
|
flex_shrink: 0.0,
|
||||||
|
flex_basis: taffy::style::Dimension::percent(1.0),
|
||||||
|
size: taffy::Size {
|
||||||
|
width: taffy::style::Dimension::percent(1.0),
|
||||||
|
height: taffy::style::Dimension::length(0.0),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
atoms.push(taffy_tree.new_leaf(breaker_style).ok()?);
|
||||||
|
} else {
|
||||||
|
atoms.extend(build_any(
|
||||||
|
tree,
|
||||||
|
rc,
|
||||||
|
taffy_tree,
|
||||||
|
id_map,
|
||||||
|
words,
|
||||||
|
engine,
|
||||||
|
ifc_items,
|
||||||
|
styles,
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if join_after {
|
if join_after {
|
||||||
atoms.extend(after_leaves.iter().copied());
|
atoms.extend(after_leaves.iter().copied());
|
||||||
@@ -11086,6 +11138,53 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn forced_break_in_pseudo_joined_inline_run_has_no_phantom_line() {
|
||||||
|
let tree = parse_html(
|
||||||
|
r#"<style>
|
||||||
|
html,body,h1,p { margin:0 }
|
||||||
|
h1 { width:600px; font:40px/60px sans-serif }
|
||||||
|
#heading::after, #consecutive::after {
|
||||||
|
content:"_"; display:inline; position:relative
|
||||||
|
}
|
||||||
|
p { width:300px; font:16px/20px sans-serif }
|
||||||
|
</style>
|
||||||
|
<h1 id="heading">first<br id="heading-break">second</h1>
|
||||||
|
<p id="consecutive">A<br><br>B</p>"#,
|
||||||
|
);
|
||||||
|
let laid = layout_dom(&tree, (800.0, 400.0));
|
||||||
|
let heading = tree.get_element_by_id("heading").unwrap();
|
||||||
|
let heading_break = tree.get_element_by_id("heading-break").unwrap();
|
||||||
|
let consecutive = tree.get_element_by_id("consecutive").unwrap();
|
||||||
|
let break_style = &laid.styles[&heading_break];
|
||||||
|
|
||||||
|
assert_eq!(break_style.display, crate::Display::Inline);
|
||||||
|
assert_eq!(break_style.width, crate::Dimension::Auto);
|
||||||
|
assert_eq!(break_style.height, crate::Dimension::Auto);
|
||||||
|
assert!(
|
||||||
|
(laid.rects[&heading].height - 120.0).abs() < 0.01,
|
||||||
|
"A<br>B plus an inline pseudo must form two 60px lines: host={:?}, br={:?}, pseudo={}, run_ifc={}",
|
||||||
|
laid.rects[&heading],
|
||||||
|
laid.rects.get(&heading_break),
|
||||||
|
laid.styles[&heading].after_pseudo.is_some(),
|
||||||
|
laid.run_ifc_items
|
||||||
|
.get(&heading)
|
||||||
|
.map_or(0, |items| items.len())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
laid.rects
|
||||||
|
.get(&heading_break)
|
||||||
|
.map_or(0.0, |rect| rect.width),
|
||||||
|
0.0,
|
||||||
|
"the mapped BR marker must not expose the anonymous breaker width"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
(laid.rects[&consecutive].height - 60.0).abs() < 0.01,
|
||||||
|
"A<br><br>B must form three 20px lines: {:?}",
|
||||||
|
laid.rects[&consecutive]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generated_inline_boxes_apply_sizes_only_after_display_blockification() {
|
fn generated_inline_boxes_apply_sizes_only_after_display_blockification() {
|
||||||
let tree = parse_html(
|
let tree = parse_html(
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ pub fn ua_style(tag: &str) -> LayoutStyle {
|
|||||||
"span" | "a" | "b" | "i" | "strong" | "em" | "font" | "code" | "small"
|
"span" | "a" | "b" | "i" | "strong" | "em" | "font" | "code" | "small"
|
||||||
| "sub" | "sup" | "mark" | "abbr" | "cite" | "var" | "dfn" | "kbd"
|
| "sub" | "sup" | "mark" | "abbr" | "cite" | "var" | "dfn" | "kbd"
|
||||||
| "samp" | "q" | "time" | "s" | "u" | "del" | "ins" | "tt" | "big"
|
| "samp" | "q" | "time" | "s" | "u" | "del" | "ins" | "tt" | "big"
|
||||||
| "bdi" | "bdo" | "wbr" | "data" | "output" | "label" | "ruby" | "rt"
|
| "bdi" | "bdo" | "br" | "wbr" | "data" | "output" | "label" | "ruby" | "rt"
|
||||||
| "rp" => Display::Inline,
|
| "rp" => Display::Inline,
|
||||||
"tr" => Display::Flex,
|
"tr" => Display::Flex,
|
||||||
_ => Display::Block,
|
_ => Display::Block,
|
||||||
@@ -67,13 +67,6 @@ pub fn ua_style(tag: &str) -> LayoutStyle {
|
|||||||
// not lay out as a real box (news CDNs put dimensions on `<source>`,
|
// not lay out as a real box (news CDNs put dimensions on `<source>`,
|
||||||
// which otherwise paints an empty box the size of the image).
|
// which otherwise paints an empty box the size of the image).
|
||||||
style.display = crate::Display::None;
|
style.display = crate::Display::None;
|
||||||
} else if tag == "br" {
|
|
||||||
// Keep forced breaks as full-width sentinels in the general
|
|
||||||
// flex/block fallback. The builder replaces the zero height with the
|
|
||||||
// inherited used line-height; pure and mixed inline runs fold the tag
|
|
||||||
// into the shaped line stream instead.
|
|
||||||
style.width = crate::Dimension::Percent(1.0);
|
|
||||||
style.height = crate::Dimension::Px(0.0);
|
|
||||||
} else if tag == "pre" {
|
} else if tag == "pre" {
|
||||||
// HTML's UA sheet preserves source whitespace in preformatted blocks.
|
// HTML's UA sheet preserves source whitespace in preformatted blocks.
|
||||||
style.white_space = Some(crate::WhiteSpace::Pre);
|
style.white_space = Some(crate::WhiteSpace::Pre);
|
||||||
|
|||||||
Reference in new issue
Block a user