Match grid replaced sizing semantics
This commit is contained in:
1 parent
c678af5bf4
commit
d5857fc889
12 files changed
+755
-97
No files matched your search
@@ -1242,6 +1242,7 @@ fn apply_presentational_hints(node: &obscura_dom::tree::Node, style: &mut crate:
|
||||
if w > 0.0 && h > 0.0 {
|
||||
style.aspect_ratio = Some(w / h);
|
||||
style.aspect_ratio_is_mapped = true;
|
||||
style.aspect_ratio_is_intrinsic = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1265,6 +1266,7 @@ fn apply_presentational_hints(node: &obscura_dom::tree::Node, style: &mut crate:
|
||||
.collect();
|
||||
if values.len() == 4 && values[2] > 0.0 && values[3] > 0.0 {
|
||||
style.aspect_ratio = Some(values[2] / values[3]);
|
||||
style.aspect_ratio_is_intrinsic = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1355,6 +1357,7 @@ fn apply_picture_source_hints(
|
||||
_ => None,
|
||||
};
|
||||
style.aspect_ratio_is_mapped = style.aspect_ratio.is_some();
|
||||
style.aspect_ratio_is_intrinsic = style.aspect_ratio.is_some();
|
||||
}
|
||||
|
||||
/// Compute the UA + author style for every element in preorder, maintaining
|
||||
@@ -1394,6 +1397,11 @@ fn cascade_walk(
|
||||
}
|
||||
let mut style = crate::style::ua_style(elem.local.as_ref());
|
||||
style.is_replaced_box = crate::inline::is_replaced(elem.local.as_ref());
|
||||
style.has_replaced_sizing = crate::inline::has_replaced_sizing(elem.local.as_ref())
|
||||
|| (elem.local.as_ref() == "input"
|
||||
&& node
|
||||
.get_attribute("type")
|
||||
.is_some_and(|value| value.eq_ignore_ascii_case("image")));
|
||||
style.color_scheme_dark = inherited_color_scheme_dark;
|
||||
if matches!(elem.local.as_ref(), "dir" | "dl" | "menu" | "ol" | "ul") {
|
||||
let mut list_ancestor_count = 0usize;
|
||||
@@ -1527,6 +1535,7 @@ fn cascade_walk(
|
||||
}
|
||||
custom_properties.insert(id, this_props.clone());
|
||||
style.is_replaced_box |= style.content_image.is_some();
|
||||
style.has_replaced_sizing |= 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(
|
||||
@@ -1545,6 +1554,7 @@ fn cascade_walk(
|
||||
.flatten()
|
||||
{
|
||||
pseudo.is_replaced_box = pseudo.content_image.is_some();
|
||||
pseudo.has_replaced_sizing = pseudo.content_image.is_some();
|
||||
}
|
||||
style.before_content = before_pseudo
|
||||
.as_ref()
|
||||
@@ -3030,7 +3040,11 @@ fn layout_dom_once(
|
||||
.filter_map(|(&id, style)| {
|
||||
let node = tree.get_node(id)?;
|
||||
let element = node.as_element()?;
|
||||
(element.local.as_ref() == "button" && style.width == crate::Dimension::Auto).then(
|
||||
(element.local.as_ref() == "button"
|
||||
&& style.width == crate::Dimension::Auto
|
||||
&& container_auto_inline_size(tree, id, style, &styles)
|
||||
!= ContainerAutoInlineSize::StretchedGridItem)
|
||||
.then(
|
||||
|| {
|
||||
let font_size = style.font_size.unwrap_or(13.333_333).max(1.0);
|
||||
(
|
||||
@@ -3041,6 +3055,24 @@ fn layout_dom_once(
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let native_control_grid_stretch: HashMap<NodeId, (bool, bool)> = styles
|
||||
.iter()
|
||||
.filter_map(|(&id, style)| {
|
||||
let node = tree.get_node(id)?;
|
||||
let element = node.as_element()?;
|
||||
matches!(element.local.as_ref(), "input" | "select").then(|| {
|
||||
(
|
||||
id,
|
||||
(
|
||||
container_auto_inline_size(tree, id, style, &styles)
|
||||
== ContainerAutoInlineSize::StretchedGridItem,
|
||||
container_auto_block_size(tree, id, style, &styles)
|
||||
== ContainerAutoBlockSize::StretchedGridItem,
|
||||
),
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
for (&id, style) in styles.iter_mut() {
|
||||
let Some(node) = tree.get_node(id) else {
|
||||
continue;
|
||||
@@ -3048,7 +3080,10 @@ fn layout_dom_once(
|
||||
let Some(element) = node.as_element() else {
|
||||
continue;
|
||||
};
|
||||
if element.local.as_ref() == "button" && style.width == crate::Dimension::Auto {
|
||||
if element.local.as_ref() == "button"
|
||||
&& style.width == crate::Dimension::Auto
|
||||
&& native_button_contents.contains_key(&id)
|
||||
{
|
||||
// Buttons remain intrinsically sized form controls even when
|
||||
// author CSS changes their inner display to flex/grid. Treating
|
||||
// `display:flex` as an ordinary block makes an auto-width
|
||||
@@ -3160,7 +3195,9 @@ fn layout_dom_once(
|
||||
let intrinsic_height =
|
||||
crate::inline::used_line_height(style).max(1.0) * rows
|
||||
+ vertical_edges;
|
||||
if style.width == crate::Dimension::Auto {
|
||||
let (stretch_inline, stretch_block) =
|
||||
native_control_grid_stretch.get(&id).copied().unwrap_or_default();
|
||||
if style.width == crate::Dimension::Auto && !stretch_inline {
|
||||
style.width = crate::Dimension::Px(
|
||||
if style.box_sizing == crate::BoxSizing::ContentBox {
|
||||
label_width
|
||||
@@ -3169,7 +3206,7 @@ fn layout_dom_once(
|
||||
},
|
||||
);
|
||||
}
|
||||
if style.height == crate::Dimension::Auto {
|
||||
if style.height == crate::Dimension::Auto && !stretch_block {
|
||||
style.height = crate::Dimension::Px(
|
||||
if style.box_sizing == crate::BoxSizing::ContentBox {
|
||||
(intrinsic_height - vertical_edges).max(0.0)
|
||||
@@ -3194,6 +3231,8 @@ fn layout_dom_once(
|
||||
}
|
||||
|
||||
let font_size = style.font_size.unwrap_or(13.333_333).max(1.0);
|
||||
let (stretch_inline, stretch_block) =
|
||||
native_control_grid_stretch.get(&id).copied().unwrap_or_default();
|
||||
let horizontal_edges = style.padding.left
|
||||
+ style.padding.right
|
||||
+ style.border.left
|
||||
@@ -3240,7 +3279,7 @@ fn layout_dom_once(
|
||||
)
|
||||
}
|
||||
};
|
||||
if style.width == crate::Dimension::Auto {
|
||||
if style.width == crate::Dimension::Auto && !stretch_inline {
|
||||
let declared_width = if style.box_sizing == crate::BoxSizing::ContentBox {
|
||||
(intrinsic_width - horizontal_edges).max(0.0)
|
||||
} else {
|
||||
@@ -3248,7 +3287,7 @@ fn layout_dom_once(
|
||||
};
|
||||
style.width = crate::Dimension::Px(declared_width);
|
||||
}
|
||||
if style.height == crate::Dimension::Auto {
|
||||
if style.height == crate::Dimension::Auto && !stretch_block {
|
||||
let declared_height = if style.box_sizing == crate::BoxSizing::ContentBox {
|
||||
(intrinsic_height - vertical_edges).max(0.0)
|
||||
} else {
|
||||
@@ -3350,6 +3389,7 @@ fn layout_dom_once(
|
||||
if s.aspect_ratio.is_none() || s.aspect_ratio_is_mapped {
|
||||
s.aspect_ratio = Some(iw / ih);
|
||||
s.aspect_ratio_is_mapped = false;
|
||||
s.aspect_ratio_is_intrinsic = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4420,7 +4460,9 @@ fn is_full_span_column_subgrid(style: &crate::LayoutStyle) -> bool {
|
||||
|| style.overflow_hidden
|
||||
|| style.position == Some(taffy::Position::Absolute)
|
||||
|| style.width != crate::Dimension::Auto
|
||||
|| style.justify_self.is_some_and(|value| value != taffy::AlignSelf::STRETCH)
|
||||
|| style
|
||||
.justify_self
|
||||
.is_some_and(|value| value.resolve_normal(taffy::AlignSelf::STRETCH) != taffy::AlignSelf::STRETCH)
|
||||
|| style.margin_auto[1]
|
||||
|| style.margin_auto[3]
|
||||
{
|
||||
@@ -6695,12 +6737,24 @@ where
|
||||
&& !matches!(style.position, Some(taffy::Position::Absolute))
|
||||
}
|
||||
taffy::Display::Grid => {
|
||||
let child_align = taffy_tree
|
||||
.style(node)
|
||||
.ok()
|
||||
let child_style = taffy_tree.style(node).ok();
|
||||
let horizontal = child_style
|
||||
.and_then(|child| child.justify_self)
|
||||
.or(parent_style.justify_items)
|
||||
.unwrap_or(taffy::AlignItems::STRETCH);
|
||||
.unwrap_or(taffy::AlignItems::NORMAL);
|
||||
let vertical = child_style
|
||||
.and_then(|child| child.align_self)
|
||||
.or(parent_style.align_items)
|
||||
.unwrap_or(taffy::AlignItems::NORMAL);
|
||||
let normal = if child_style.is_some_and(|child| child.item_is_replaced)
|
||||
|| (child_style.is_some_and(|child| child.aspect_ratio.is_some())
|
||||
&& vertical == taffy::AlignItems::STRETCH)
|
||||
{
|
||||
taffy::AlignItems::START
|
||||
} else {
|
||||
taffy::AlignItems::STRETCH
|
||||
};
|
||||
let child_align = horizontal.resolve_normal(normal);
|
||||
child_align == taffy::AlignItems::STRETCH
|
||||
}
|
||||
taffy::Display::Flex
|
||||
@@ -6715,7 +6769,8 @@ where
|
||||
.ok()
|
||||
.and_then(|child| child.align_self)
|
||||
.or(parent_style.align_items)
|
||||
.unwrap_or(taffy::AlignItems::STRETCH);
|
||||
.unwrap_or(taffy::AlignItems::NORMAL)
|
||||
.resolve_normal(taffy::AlignItems::STRETCH);
|
||||
child_align == taffy::AlignItems::STRETCH
|
||||
}
|
||||
_ => false,
|
||||
@@ -7095,6 +7150,57 @@ fn effective_container_type(style: &crate::LayoutStyle) -> crate::ContainerType
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn used_flex_alignment(value: Option<taffy::AlignSelf>, parent: Option<taffy::AlignItems>) -> taffy::AlignSelf {
|
||||
value
|
||||
.or(parent)
|
||||
.unwrap_or(taffy::AlignSelf::NORMAL)
|
||||
.resolve_normal(taffy::AlignSelf::STRETCH)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn used_grid_alignments(
|
||||
style: &crate::LayoutStyle,
|
||||
parent: &crate::LayoutStyle,
|
||||
) -> (taffy::AlignSelf, taffy::AlignSelf) {
|
||||
let horizontal = style.justify_self.or(parent.justify_items).unwrap_or(taffy::AlignSelf::NORMAL);
|
||||
let vertical = style.align_self.or(parent.align_items).unwrap_or(taffy::AlignSelf::NORMAL);
|
||||
if style.has_replaced_sizing {
|
||||
return (
|
||||
horizontal.resolve_normal(taffy::AlignSelf::START),
|
||||
vertical.resolve_normal(taffy::AlignSelf::START),
|
||||
);
|
||||
}
|
||||
if style.aspect_ratio.is_none() {
|
||||
return (
|
||||
horizontal.resolve_normal(taffy::AlignSelf::STRETCH),
|
||||
vertical.resolve_normal(taffy::AlignSelf::STRETCH),
|
||||
);
|
||||
}
|
||||
|
||||
let horizontal_is_normal = horizontal == taffy::AlignSelf::NORMAL;
|
||||
let vertical_is_normal = vertical == taffy::AlignSelf::NORMAL;
|
||||
let horizontal = if horizontal_is_normal {
|
||||
if !vertical_is_normal && vertical == taffy::AlignSelf::STRETCH {
|
||||
taffy::AlignSelf::START
|
||||
} else {
|
||||
taffy::AlignSelf::STRETCH
|
||||
}
|
||||
} else {
|
||||
horizontal
|
||||
};
|
||||
let vertical = if vertical_is_normal {
|
||||
if !horizontal_is_normal && horizontal != taffy::AlignSelf::STRETCH {
|
||||
taffy::AlignSelf::STRETCH
|
||||
} else {
|
||||
taffy::AlignSelf::START
|
||||
}
|
||||
} else {
|
||||
vertical
|
||||
};
|
||||
(horizontal, vertical)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum ContainerAutoInlineSize {
|
||||
FillAvailable,
|
||||
@@ -7117,7 +7223,11 @@ fn container_auto_inline_size(
|
||||
style: &crate::LayoutStyle,
|
||||
styles: &HashMap<NodeId, crate::LayoutStyle>,
|
||||
) -> ContainerAutoInlineSize {
|
||||
if style.is_inline_block || style.float.is_some() {
|
||||
// Grid items are blockified and their float is ignored. Their original
|
||||
// inline-block/float provenance must not suppress grid self-alignment.
|
||||
if (style.is_inline_block || style.float.is_some())
|
||||
&& !is_in_flow_grid_item(tree, id, style, styles)
|
||||
{
|
||||
return ContainerAutoInlineSize::Intrinsic;
|
||||
}
|
||||
if matches!(style.position, Some(taffy::Position::Absolute)) {
|
||||
@@ -7155,11 +7265,8 @@ fn container_auto_inline_size(
|
||||
);
|
||||
if row {
|
||||
ContainerAutoInlineSize::Intrinsic
|
||||
} else if style.align_self.unwrap_or(
|
||||
parent_style
|
||||
.align_items
|
||||
.unwrap_or(taffy::AlignItems::STRETCH),
|
||||
) == taffy::AlignSelf::STRETCH
|
||||
} else if used_flex_alignment(style.align_self, parent_style.align_items)
|
||||
== taffy::AlignSelf::STRETCH
|
||||
{
|
||||
ContainerAutoInlineSize::FillAvailable
|
||||
} else {
|
||||
@@ -7167,11 +7274,9 @@ fn container_auto_inline_size(
|
||||
}
|
||||
}
|
||||
crate::Display::Grid => {
|
||||
if style.justify_self.unwrap_or(
|
||||
parent_style
|
||||
.justify_items
|
||||
.unwrap_or(taffy::JustifyItems::STRETCH),
|
||||
) == taffy::AlignSelf::STRETCH
|
||||
if used_grid_alignments(style, parent_style).0 == taffy::AlignSelf::STRETCH
|
||||
&& !style.margin_auto[1]
|
||||
&& !style.margin_auto[3]
|
||||
{
|
||||
ContainerAutoInlineSize::StretchedGridItem
|
||||
} else {
|
||||
@@ -7215,11 +7320,7 @@ fn container_auto_block_size(
|
||||
};
|
||||
|
||||
if parent_style.display == crate::Display::Grid
|
||||
&& style.align_self.unwrap_or(
|
||||
parent_style
|
||||
.align_items
|
||||
.unwrap_or(taffy::AlignItems::STRETCH),
|
||||
) == taffy::AlignSelf::STRETCH
|
||||
&& used_grid_alignments(style, parent_style).1 == taffy::AlignSelf::STRETCH
|
||||
&& !style.margin_auto[0]
|
||||
&& !style.margin_auto[2]
|
||||
{
|
||||
@@ -7513,6 +7614,27 @@ fn resolve_deferred_flex_inline_sizes(
|
||||
true
|
||||
}
|
||||
|
||||
fn is_in_flow_grid_item(
|
||||
tree: &DomTree,
|
||||
id: NodeId,
|
||||
style: &crate::LayoutStyle,
|
||||
styles: &HashMap<NodeId, crate::LayoutStyle>,
|
||||
) -> bool {
|
||||
if matches!(style.position, Some(taffy::Position::Absolute)) {
|
||||
return false;
|
||||
}
|
||||
let mut parent = tree.get_node(id).and_then(|node| node.parent);
|
||||
loop {
|
||||
let Some(parent_id) = parent else { return false };
|
||||
let Some(parent_style) = styles.get(&parent_id) else { return false };
|
||||
if parent_style.display_contents {
|
||||
parent = tree.get_node(parent_id).and_then(|node| node.parent);
|
||||
continue;
|
||||
}
|
||||
return parent_style.display == crate::Display::Grid;
|
||||
}
|
||||
}
|
||||
|
||||
fn build(
|
||||
tree: &DomTree,
|
||||
id: NodeId,
|
||||
@@ -7572,19 +7694,7 @@ fn build(
|
||||
if matches!(style.max_width, crate::Dimension::Percent(_))
|
||||
&& matches!(style.min_width, crate::Dimension::Auto)
|
||||
{
|
||||
let mut parent = node.parent;
|
||||
let grid_item = loop {
|
||||
let Some(parent_id) = parent else { break false };
|
||||
let Some(parent_style) = styles.get(&parent_id) else {
|
||||
break false;
|
||||
};
|
||||
if parent_style.display_contents {
|
||||
parent = tree.get_node(parent_id).and_then(|node| node.parent);
|
||||
continue;
|
||||
}
|
||||
break parent_style.display == crate::Display::Grid;
|
||||
};
|
||||
if grid_item {
|
||||
if is_in_flow_grid_item(tree, id, style, styles) {
|
||||
taffy_style.min_size.width = taffy::Dimension::length(0.0);
|
||||
}
|
||||
}
|
||||
@@ -7685,6 +7795,21 @@ fn build(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((width, height)) =
|
||||
crate::inline::default_replaced_intrinsic_size(
|
||||
_name.local.as_ref(),
|
||||
style.font_size.unwrap_or(16.0),
|
||||
node.get_attribute("controls").is_some(),
|
||||
_name.local.as_ref() != "embed"
|
||||
|| node.get_attribute("src").is_some_and(|value| !value.trim().is_empty()),
|
||||
)
|
||||
{
|
||||
let context = engine.register_replaced(width, height, style);
|
||||
let leaf = taffy_tree.new_leaf_with_context(taffy_style, context).ok()?;
|
||||
id_map.insert(leaf, id);
|
||||
return Some(leaf);
|
||||
}
|
||||
|
||||
// A replaced image is a measured leaf, even when CSS gives it a percentage
|
||||
// width. Its intrinsic dimensions participate in an auto-sized ancestor's
|
||||
// max-content measurement; once the percentage axis becomes definite, the
|
||||
@@ -7762,6 +7887,7 @@ fn build(
|
||||
if matches!(style.width, crate::Dimension::Auto)
|
||||
&& matches!(style.height, crate::Dimension::Auto)
|
||||
&& !has_percentage_constraint
|
||||
&& !is_in_flow_grid_item(tree, id, style, styles)
|
||||
{
|
||||
let constrained =
|
||||
crate::inline::constrained_auto_replaced_size(width, height, style);
|
||||
@@ -8589,10 +8715,7 @@ fn needs_column_flex_text_fit_content_cap(
|
||||
return false;
|
||||
}
|
||||
|
||||
style
|
||||
.align_self
|
||||
.unwrap_or(parent_style.align_items.unwrap_or(taffy::AlignItems::STRETCH))
|
||||
!= taffy::AlignSelf::STRETCH
|
||||
used_flex_alignment(style.align_self, parent_style.align_items) != taffy::AlignSelf::STRETCH
|
||||
}
|
||||
|
||||
/// Expand `display: contents` wrappers so their children partition into the
|
||||
|
||||
@@ -1974,6 +1974,37 @@ pub(crate) fn is_replaced(local: &str) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
/// Elements that use CSS's intrinsic replaced-size algorithm. This is
|
||||
/// deliberately narrower than `is_replaced`: ordinary form controls are
|
||||
/// atomic inline boxes, but grid `normal` stretches them like non-replaced
|
||||
/// boxes in Chromium and Gecko.
|
||||
pub(crate) fn has_replaced_sizing(local: &str) -> bool {
|
||||
matches!(
|
||||
local,
|
||||
"img" | "canvas" | "video" | "audio" | "iframe" | "embed"
|
||||
| "object" | "progress" | "meter"
|
||||
)
|
||||
}
|
||||
|
||||
/// HTML's default object size for replaced media whose intrinsic metadata is
|
||||
/// not available yet. Canvas dimensions and decoded video metadata can replace
|
||||
/// these defaults before layout when present.
|
||||
pub(crate) fn default_replaced_intrinsic_size(
|
||||
local: &str,
|
||||
font_size: f32,
|
||||
has_controls: bool,
|
||||
has_resource: bool,
|
||||
) -> Option<(f32, f32)> {
|
||||
match local {
|
||||
"canvas" | "video" | "iframe" | "object" => Some((300.0, 150.0)),
|
||||
"embed" if has_resource => Some((300.0, 150.0)),
|
||||
"audio" if has_controls => Some((300.0, 54.0)),
|
||||
"progress" => Some((font_size * 10.0, font_size)),
|
||||
"meter" => Some((font_size * 5.0, font_size)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Is `cid` (and its whole subtree) inline-level, in-flow content safe to fold
|
||||
/// into a shaped buffer? Sets `has_text` if it contributes any non-whitespace
|
||||
/// text. Inline wrappers (`<a>`, `<span>`, `<b>`, `<code>`, `<sup>`, ...) are
|
||||
|
||||
@@ -534,6 +534,9 @@ pub struct LayoutStyle {
|
||||
/// missing dimension from the given one, so a `width:100%` image gets a
|
||||
/// real height instead of collapsing to zero.
|
||||
pub aspect_ratio: Option<f32>,
|
||||
/// Whether the preferred ratio came from decoded intrinsic media and
|
||||
/// therefore applies to the content box regardless of `box-sizing`.
|
||||
pub aspect_ratio_is_intrinsic: bool,
|
||||
/// Fetched intrinsic CSS-pixel size for a replaced element. Kept alongside
|
||||
/// `aspect_ratio` so its taffy leaf can contribute a real min/max-content
|
||||
/// size when percentage dimensions are resolved through an auto-sized
|
||||
@@ -551,6 +554,11 @@ pub struct LayoutStyle {
|
||||
/// cannot be inferred from intrinsic dimensions because an unloaded or
|
||||
/// broken image remains replaced.
|
||||
pub(crate) is_replaced_box: bool,
|
||||
/// Whether this element uses the intrinsic replaced-element sizing
|
||||
/// algorithm. This is narrower than `is_replaced_box`: form controls such
|
||||
/// as buttons are atomic inline boxes but still use ordinary grid `normal`
|
||||
/// stretch behavior in Chromium and Gecko.
|
||||
pub(crate) has_replaced_sizing: 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
|
||||
@@ -1360,6 +1368,8 @@ fn read_node(tree: &TaffyTree, id: NodeId) -> NodeRect {
|
||||
|
||||
pub(crate) fn to_taffy_style(style: &LayoutStyle) -> Style {
|
||||
let mut s = Style::DEFAULT;
|
||||
s.item_is_replaced = style.has_replaced_sizing;
|
||||
s.item_aspect_ratio_is_intrinsic = style.aspect_ratio_is_intrinsic;
|
||||
s.box_sizing = match style.box_sizing {
|
||||
BoxSizing::ContentBox => taffy::BoxSizing::ContentBox,
|
||||
BoxSizing::BorderBox => taffy::BoxSizing::BorderBox,
|
||||
|
||||
@@ -813,6 +813,7 @@ fn css_color([r, g, b, a]: [u8; 4]) -> String {
|
||||
|
||||
fn align_items_css(value: taffy::AlignItems) -> String {
|
||||
let keyword = match value.keyword {
|
||||
taffy::AlignItemsKeyword::Normal => "normal",
|
||||
taffy::AlignItemsKeyword::Start => "start",
|
||||
taffy::AlignItemsKeyword::End => "end",
|
||||
taffy::AlignItemsKeyword::FlexStart => "flex-start",
|
||||
|
||||
@@ -125,6 +125,11 @@ pub fn ua_style(tag: &str) -> LayoutStyle {
|
||||
} else if tag == "a" {
|
||||
style.color = Some([0, 0, 238, 255]); // blue
|
||||
style.underline = Some(true); // UA default: links are underlined
|
||||
} else if tag == "iframe" {
|
||||
// HTML's UA sheet gives frames a two-pixel inset border. The paint
|
||||
// model does not distinguish inset yet, but border-box geometry still
|
||||
// includes these edges around the 300x150 default object size.
|
||||
style.border = Edges { top: 2.0, right: 2.0, bottom: 2.0, left: 2.0 };
|
||||
} else if tag == "button" {
|
||||
// HTML buttons are inline-block controls whose anonymous inner
|
||||
// content is centered by the browser UA sheet. Keep the atomic outer
|
||||
@@ -829,6 +834,7 @@ fn apply_value(style: &mut LayoutStyle, name: &str, value: &str) {
|
||||
"aspect-ratio" => {
|
||||
style.aspect_ratio = parse_aspect_ratio(value);
|
||||
style.aspect_ratio_is_mapped = false;
|
||||
style.aspect_ratio_is_intrinsic = false;
|
||||
}
|
||||
"margin" => apply_margin_shorthand(style, value),
|
||||
"margin-top" => set_margin_side(style, 0, value),
|
||||
@@ -2252,7 +2258,7 @@ fn self_alignment_value(value: &str) -> Option<Option<taffy::AlignSelf>> {
|
||||
let normalized = value.trim().to_ascii_lowercase();
|
||||
let alignment = match normalized.as_str() {
|
||||
"auto" => return Some(None),
|
||||
"normal" => taffy::AlignSelf::STRETCH,
|
||||
"normal" => taffy::AlignSelf::NORMAL,
|
||||
"start" | "self-start" => taffy::AlignSelf::START,
|
||||
"end" | "self-end" => taffy::AlignSelf::END,
|
||||
"flex-start" => taffy::AlignSelf::FLEX_START,
|
||||
@@ -6193,8 +6199,8 @@ mod tests {
|
||||
assert_eq!(reset.justify_self, None);
|
||||
|
||||
let normal = compute_style("div", Some("align-self:normal;justify-self:normal"));
|
||||
assert_eq!(normal.align_self, Some(taffy::AlignSelf::STRETCH));
|
||||
assert_eq!(normal.justify_self, Some(taffy::JustifySelf::STRETCH));
|
||||
assert_eq!(normal.align_self, Some(taffy::AlignSelf::NORMAL));
|
||||
assert_eq!(normal.justify_self, Some(taffy::JustifySelf::NORMAL));
|
||||
|
||||
let shorthand = compute_style("div", Some("place-self:safe center flex-end"));
|
||||
assert_eq!(shorthand.align_self, Some(taffy::AlignSelf::SAFE_CENTER));
|
||||
|
||||
@@ -3816,3 +3816,191 @@ fn closed_details_content_does_not_paint() {
|
||||
"closed details content must leave the page background untouched"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grid_replaced_normal_and_explicit_stretch_match_browser_geometry() {
|
||||
let tree = parse_html(
|
||||
r#"
|
||||
<style>
|
||||
html, body { margin:0 }
|
||||
.grid { display:grid; grid-template:200px / 300px; width:300px; height:200px }
|
||||
</style>
|
||||
<div class="grid"><img id="normal"></div>
|
||||
<div class="grid"><img id="min-inline" style="min-width:50%"></div>
|
||||
<div class="grid"><img id="min-block" style="min-height:50%"></div>
|
||||
<div class="grid"><img id="normal-inline" style="justify-self:normal;min-width:50%"></div>
|
||||
<div class="grid"><img id="start" style="justify-self:start;min-width:50%"></div>
|
||||
<div class="grid"><img id="auto-margin" style="min-width:50%;margin-left:auto"></div>
|
||||
<div class="grid"><img id="stretch-inline" style="justify-self:stretch"></div>
|
||||
<div class="grid"><img id="stretch-block" style="align-self:stretch"></div>
|
||||
<div class="grid"><img id="stretch-both" style="place-self:stretch"></div>
|
||||
<div class="grid"><img id="definite-inline" style="width:120px;align-self:stretch"></div>
|
||||
<div class="grid"><img id="definite-block" style="height:80px;justify-self:stretch"></div>
|
||||
<div class="grid" style="justify-items:stretch"><img id="parent-stretch"></div>
|
||||
<div class="grid" style="justify-items:normal"><img id="parent-normal" style="min-width:50%"></div>
|
||||
<div class="grid"><img id="intrinsic-border-box" style="box-sizing:border-box;width:120px;padding:10px;border:5px solid"></div>
|
||||
<div class="grid"><img id="intrinsic-content-box" style="box-sizing:content-box;width:120px;padding:10px;border:5px solid"></div>
|
||||
<div class="grid"><img id="authored-border-box" style="box-sizing:border-box;width:120px;padding:10px;border:5px solid;aspect-ratio:1"></div>
|
||||
<div class="grid"><img id="authored-content-box" style="box-sizing:content-box;width:120px;padding:10px;border:5px solid;aspect-ratio:1"></div>
|
||||
<div class="grid"><img id="conflicting-min-inline" style="min-width:250px;max-height:80px"></div>
|
||||
<div class="grid"><img id="conflicting-min-block" style="max-width:120px;min-height:100px"></div>
|
||||
"#,
|
||||
);
|
||||
let ids = [
|
||||
"normal",
|
||||
"min-inline",
|
||||
"min-block",
|
||||
"normal-inline",
|
||||
"start",
|
||||
"auto-margin",
|
||||
"stretch-inline",
|
||||
"stretch-block",
|
||||
"stretch-both",
|
||||
"definite-inline",
|
||||
"definite-block",
|
||||
"parent-stretch",
|
||||
"parent-normal",
|
||||
"intrinsic-border-box",
|
||||
"intrinsic-content-box",
|
||||
"authored-border-box",
|
||||
"authored-content-box",
|
||||
"conflicting-min-inline",
|
||||
"conflicting-min-block",
|
||||
];
|
||||
let intrinsic: HashMap<_, _> = ids
|
||||
.iter()
|
||||
.map(|name| (tree.get_element_by_id(name).unwrap(), (100.0, 50.0)))
|
||||
.collect();
|
||||
let layout = layout_dom_with_images(&tree, (800.0, 3000.0), &intrinsic);
|
||||
let rect = |name| layout.rects[&tree.get_element_by_id(name).unwrap()];
|
||||
let size = |name| {
|
||||
let rect = rect(name);
|
||||
(rect.width, rect.height)
|
||||
};
|
||||
|
||||
assert_eq!(size("normal"), (100.0, 50.0));
|
||||
assert_eq!(size("min-inline"), (150.0, 75.0));
|
||||
assert_eq!(size("min-block"), (200.0, 100.0));
|
||||
assert_eq!(size("normal-inline"), (150.0, 75.0));
|
||||
assert_eq!(size("start"), (150.0, 75.0));
|
||||
assert_eq!(size("auto-margin"), (150.0, 75.0));
|
||||
assert_eq!(size("stretch-inline"), (300.0, 150.0));
|
||||
assert_eq!(size("stretch-block"), (400.0, 200.0));
|
||||
assert_eq!(size("stretch-both"), (300.0, 200.0));
|
||||
assert_eq!(size("definite-inline"), (120.0, 200.0));
|
||||
assert_eq!(size("definite-block"), (300.0, 80.0));
|
||||
assert_eq!(size("parent-stretch"), (300.0, 150.0));
|
||||
assert_eq!(size("parent-normal"), (150.0, 75.0));
|
||||
assert_eq!(size("intrinsic-border-box"), (120.0, 75.0));
|
||||
assert_eq!(size("intrinsic-content-box"), (150.0, 90.0));
|
||||
assert_eq!(size("authored-border-box"), (120.0, 120.0));
|
||||
assert_eq!(size("authored-content-box"), (150.0, 150.0));
|
||||
assert_eq!(size("conflicting-min-inline"), (250.0, 80.0));
|
||||
assert_eq!(size("conflicting-min-block"), (120.0, 100.0));
|
||||
|
||||
let item = rect("auto-margin");
|
||||
let parent = tree
|
||||
.get_node(tree.get_element_by_id("auto-margin").unwrap())
|
||||
.and_then(|node| node.parent)
|
||||
.unwrap();
|
||||
assert_eq!(item.x - layout.rects[&parent].x, 150.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grid_replaced_classification_keeps_controls_stretched_and_media_natural() {
|
||||
let tree = parse_html(
|
||||
r#"
|
||||
<style>
|
||||
html, body { margin:0 }
|
||||
.grid { display:grid; grid-template:300px / 400px; width:400px; height:300px }
|
||||
</style>
|
||||
<div class="grid"><svg id="svg" viewBox="0 0 100 50"></svg></div>
|
||||
<div class="grid"><svg id="svg-border-box" viewBox="0 0 100 50" style="box-sizing:border-box;width:120px;padding:10px;border:5px solid"></svg></div>
|
||||
<div class="grid"><img id="mapped-border-box" width="100" height="50" style="box-sizing:border-box;width:120px;height:auto;padding:10px;border:5px solid"></div>
|
||||
<div class="grid"><canvas id="canvas"></canvas></div>
|
||||
<div class="grid"><video id="video"></video></div>
|
||||
<div class="grid"><audio id="audio"></audio></div>
|
||||
<div class="grid"><audio id="audio-controls" controls></audio></div>
|
||||
<div class="grid"><iframe id="iframe"></iframe></div>
|
||||
<div class="grid"><embed id="embed" src="about:blank"></embed></div>
|
||||
<div class="grid"><embed id="empty-embed"></embed></div>
|
||||
<div class="grid"><object id="object"></object></div>
|
||||
<div class="grid"><progress id="progress"></progress></div>
|
||||
<div class="grid"><meter id="meter"></meter></div>
|
||||
<div class="grid"><input id="image-input" type="image" width="100" height="50"></div>
|
||||
<div class="grid"><button id="button">Hi</button></div>
|
||||
<div class="grid" id="auto-button-grid"><button id="auto-button" style="margin-left:auto">Hi</button></div>
|
||||
<div class="grid"><input id="text-input"></div>
|
||||
<div class="grid"><select id="select"><option>Hi</option></select></div>
|
||||
"#,
|
||||
);
|
||||
let layout = layout_dom(&tree, (800.0, 5000.0));
|
||||
let size = |name| {
|
||||
let rect = layout.rects[&tree.get_element_by_id(name).unwrap()];
|
||||
(rect.width, rect.height)
|
||||
};
|
||||
|
||||
assert_eq!(size("svg"), (400.0, 200.0));
|
||||
assert_eq!(size("svg-border-box"), (120.0, 75.0));
|
||||
assert_eq!(size("mapped-border-box"), (120.0, 75.0));
|
||||
for name in ["canvas", "video", "embed", "object"] {
|
||||
assert_eq!(size(name), (300.0, 150.0), "{name}");
|
||||
}
|
||||
assert_eq!(size("iframe"), (304.0, 154.0));
|
||||
assert_eq!(size("empty-embed"), (0.0, 0.0));
|
||||
assert_eq!(size("audio"), (0.0, 0.0));
|
||||
assert_eq!(size("audio-controls"), (300.0, 54.0));
|
||||
assert_eq!(size("progress"), (160.0, 16.0));
|
||||
assert_eq!(size("meter"), (80.0, 16.0));
|
||||
assert_eq!(size("image-input"), (100.0, 50.0));
|
||||
assert_eq!(
|
||||
layout.styles[&tree.get_element_by_id("button").unwrap()].width,
|
||||
obscura_render::Dimension::Auto,
|
||||
"grid-normal stretch must preserve the button's auto inline size"
|
||||
);
|
||||
for name in ["button", "text-input", "select"] {
|
||||
assert_eq!(size(name), (400.0, 300.0), "{name}");
|
||||
}
|
||||
let auto_button = layout.rects[&tree.get_element_by_id("auto-button").unwrap()];
|
||||
let auto_button_grid = layout.rects[&tree.get_element_by_id("auto-button-grid").unwrap()];
|
||||
assert!(auto_button.width > 20.0 && auto_button.width < 30.0);
|
||||
assert_eq!(auto_button.height, 300.0);
|
||||
assert_eq!(auto_button.x + auto_button.width, auto_button_grid.x + auto_button_grid.width);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grid_ordinary_aspect_ratio_preserves_normal_alignment_provenance() {
|
||||
let tree = parse_html(
|
||||
r#"
|
||||
<style>
|
||||
html, body { margin:0 }
|
||||
.grid { display:grid; grid-template:200px / 300px; width:300px; height:200px }
|
||||
.item { aspect-ratio:2 }
|
||||
</style>
|
||||
<div class="grid"><div class="item" id="normal"></div></div>
|
||||
<div class="grid"><div class="item" id="align-stretch" style="align-self:stretch"></div></div>
|
||||
<div class="grid"><div class="item" id="justify-stretch" style="justify-self:stretch"></div></div>
|
||||
<div class="grid"><div class="item" id="both-stretch" style="place-self:stretch"></div></div>
|
||||
<div class="grid"><div class="item" id="both-normal" style="place-self:normal"></div></div>
|
||||
<div class="grid"><div class="item" id="align-start" style="align-self:start"></div></div>
|
||||
<div class="grid"><div class="item" id="justify-start" style="justify-self:start"></div></div>
|
||||
<div class="grid" style="align-items:stretch"><div class="item" id="parent-align-stretch"></div></div>
|
||||
<div class="grid" style="justify-items:stretch"><div class="item" id="parent-justify-stretch"></div></div>
|
||||
"#,
|
||||
);
|
||||
let layout = layout_dom(&tree, (800.0, 2000.0));
|
||||
let size = |name| {
|
||||
let rect = layout.rects[&tree.get_element_by_id(name).unwrap()];
|
||||
(rect.width, rect.height)
|
||||
};
|
||||
|
||||
assert_eq!(size("normal"), (300.0, 150.0));
|
||||
assert_eq!(size("align-stretch"), (400.0, 200.0));
|
||||
assert_eq!(size("justify-stretch"), (300.0, 150.0));
|
||||
assert_eq!(size("both-stretch"), (300.0, 200.0));
|
||||
assert_eq!(size("both-normal"), (300.0, 150.0));
|
||||
assert_eq!(size("align-start"), (300.0, 150.0));
|
||||
assert_eq!(size("justify-start"), (400.0, 200.0));
|
||||
assert_eq!(size("parent-align-stretch"), (400.0, 200.0));
|
||||
assert_eq!(size("parent-justify-stretch"), (300.0, 150.0));
|
||||
}
|
||||
Vendored
+23
-6
@@ -19,6 +19,11 @@ use super::common::alignment::apply_alignment_fallback;
|
||||
#[cfg(feature = "content_size")]
|
||||
use super::common::content_size::compute_content_size_contribution;
|
||||
|
||||
#[inline]
|
||||
fn resolve_flex_normal(alignment: AlignSelf) -> AlignSelf {
|
||||
if alignment.keyword == AlignItemsKeyword::Normal { AlignSelf::STRETCH } else { alignment }
|
||||
}
|
||||
|
||||
/// The intermediate results of a flexbox calculation for a single item
|
||||
struct FlexItem {
|
||||
/// The identifier for the associated node
|
||||
@@ -451,7 +456,7 @@ fn compute_constants(
|
||||
let box_sizing_adjustment =
|
||||
if style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
|
||||
|
||||
let align_items = style.align_items().unwrap_or(AlignItems::STRETCH);
|
||||
let align_items = resolve_flex_normal(style.align_items().unwrap_or(AlignItems::STRETCH));
|
||||
let align_content = style.align_content().unwrap_or(AlignContent::STRETCH);
|
||||
let justify_content = style.justify_content();
|
||||
let layout_direction = style.direction();
|
||||
@@ -569,7 +574,7 @@ fn generate_anonymous_flex_items(
|
||||
border: child_style
|
||||
.border()
|
||||
.resolve_or_zero(constants.node_inner_size.width, |val, basis| tree.calc(val, basis)),
|
||||
align_self: child_style.align_self().unwrap_or(constants.align_items),
|
||||
align_self: resolve_flex_normal(child_style.align_self().unwrap_or(constants.align_items)),
|
||||
overflow: child_style.overflow(),
|
||||
scrollbar_width: child_style.scrollbar_width(),
|
||||
flex_grow: child_style.flex_grow(),
|
||||
@@ -1847,7 +1852,7 @@ fn align_flex_items_along_cross_axis(
|
||||
}
|
||||
}
|
||||
}
|
||||
AlignItemsKeyword::Stretch => {
|
||||
AlignItemsKeyword::Normal | AlignItemsKeyword::Stretch => {
|
||||
if constants.is_wrap_reverse ^ cross_axis_should_reverse {
|
||||
free_space
|
||||
} else {
|
||||
@@ -2182,7 +2187,7 @@ fn perform_absolute_layout_on_absolute_children(
|
||||
let overflow = child_style.overflow();
|
||||
let scrollbar_width = child_style.scrollbar_width();
|
||||
let aspect_ratio = child_style.aspect_ratio();
|
||||
let align_self = child_style.align_self().unwrap_or(constants.align_items);
|
||||
let align_self = resolve_flex_normal(child_style.align_self().unwrap_or(constants.align_items));
|
||||
let margin = child_style
|
||||
.margin()
|
||||
.map(|margin| margin.resolve_to_option(inset_relative_size.width, |val, basis| tree.calc(val, basis)));
|
||||
@@ -2453,11 +2458,23 @@ fn perform_absolute_layout_on_absolute_children(
|
||||
(AlignItemsKeyword::End, true) => {
|
||||
constants.content_box_inset.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
|
||||
}
|
||||
(AlignItemsKeyword::Baseline | AlignItemsKeyword::Stretch | AlignItemsKeyword::FlexStart, false)
|
||||
(
|
||||
AlignItemsKeyword::Normal
|
||||
| AlignItemsKeyword::Baseline
|
||||
| AlignItemsKeyword::Stretch
|
||||
| AlignItemsKeyword::FlexStart,
|
||||
false,
|
||||
)
|
||||
| (AlignItemsKeyword::FlexEnd, true) => {
|
||||
constants.content_box_inset.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
|
||||
}
|
||||
(AlignItemsKeyword::Baseline | AlignItemsKeyword::Stretch | AlignItemsKeyword::FlexStart, true)
|
||||
(
|
||||
AlignItemsKeyword::Normal
|
||||
| AlignItemsKeyword::Baseline
|
||||
| AlignItemsKeyword::Stretch
|
||||
| AlignItemsKeyword::FlexStart,
|
||||
true,
|
||||
)
|
||||
| (AlignItemsKeyword::FlexEnd, false) => {
|
||||
constants.container_size.cross(constants.dir)
|
||||
- constants.content_box_inset.cross_end(constants.dir)
|
||||
|
||||
+187
-30
@@ -103,11 +103,12 @@ pub(super) fn align_and_position_item(
|
||||
|
||||
let box_sizing_adjustment =
|
||||
if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
|
||||
let aspect_ratio_adjustment =
|
||||
if style.aspect_ratio_uses_content_box() { padding_border_size } else { box_sizing_adjustment };
|
||||
|
||||
let inherent_size = style
|
||||
.size()
|
||||
.maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
|
||||
.maybe_apply_aspect_ratio(aspect_ratio)
|
||||
.maybe_add(box_sizing_adjustment);
|
||||
let min_size = style
|
||||
.min_size()
|
||||
@@ -122,26 +123,15 @@ pub(super) fn align_and_position_item(
|
||||
.maybe_apply_aspect_ratio(aspect_ratio)
|
||||
.maybe_add(box_sizing_adjustment);
|
||||
|
||||
// Resolve default alignment styles if they are set on neither the parent or the node itself
|
||||
// Note: if the child has a preferred aspect ratio but neither width or height are set, then the width is stretched
|
||||
// and the then height is calculated from the width according the aspect ratio
|
||||
// See: https://www.w3.org/TR/css-grid-1/#grid-item-sizing
|
||||
let alignment_styles = InBothAbsAxis {
|
||||
horizontal: justify_self.or(container_alignment_styles.horizontal).unwrap_or_else(|| {
|
||||
if inherent_size.width.is_some() {
|
||||
AlignSelf::START
|
||||
} else {
|
||||
AlignSelf::STRETCH
|
||||
}
|
||||
}),
|
||||
vertical: align_self.or(container_alignment_styles.vertical).unwrap_or_else(|| {
|
||||
if inherent_size.height.is_some() || aspect_ratio.is_some() {
|
||||
AlignSelf::START
|
||||
} else {
|
||||
AlignSelf::STRETCH
|
||||
}
|
||||
}),
|
||||
};
|
||||
// Preserve `normal` provenance until both axes and the preferred aspect
|
||||
// ratio are known. Explicit stretch in one axis can make the other normal
|
||||
// axis ratio-derived, while two normal axes prefer inline-axis stretch.
|
||||
let alignment_styles = super::resolve_item_alignment(
|
||||
justify_self.or(container_alignment_styles.horizontal).unwrap_or(AlignSelf::NORMAL),
|
||||
align_self.or(container_alignment_styles.vertical).unwrap_or(AlignSelf::NORMAL),
|
||||
style.is_compressible_replaced(),
|
||||
aspect_ratio.is_some(),
|
||||
);
|
||||
|
||||
// Note: This is not a bug. It is part of the CSS spec that both horizontal and vertical margins
|
||||
// resolve against the WIDTH of the grid area.
|
||||
@@ -179,10 +169,7 @@ pub(super) fn align_and_position_item(
|
||||
None
|
||||
});
|
||||
|
||||
// Reapply aspect ratio after stretch and absolute position width adjustments
|
||||
let Size { width, height } = Size { width, height: inherent_size.height }.maybe_apply_aspect_ratio(aspect_ratio);
|
||||
|
||||
let height = height.or_else(|| {
|
||||
let height = inherent_size.height.or_else(|| {
|
||||
if position == Position::Absolute {
|
||||
if let (Some(top), Some(bottom)) = (inset_vertical.start, inset_vertical.end) {
|
||||
return Some(f32_max(grid_area_minus_item_margins_size.height - top - bottom, 0.0));
|
||||
@@ -203,8 +190,12 @@ pub(super) fn align_and_position_item(
|
||||
|
||||
None
|
||||
});
|
||||
// Reapply aspect ratio after stretch and absolute position height adjustments
|
||||
let Size { width, height } = Size { width, height }.maybe_apply_aspect_ratio(aspect_ratio);
|
||||
// Stretch is resolved before preferred-ratio transfer. If both axes are
|
||||
// stretched (or otherwise definite), the ratio does not overwrite either
|
||||
// one. If only one axis is definite, it supplies the other through the
|
||||
// ratio. This matches replaced grid sizing in Blink and Gecko.
|
||||
let Size { width, height } =
|
||||
super::apply_preferred_aspect_ratio(Size { width, height }, aspect_ratio, aspect_ratio_adjustment);
|
||||
|
||||
// Clamp size by min and max width/height
|
||||
let Size { width, height } = Size { width, height }.maybe_clamp(min_size, max_size);
|
||||
@@ -290,7 +281,7 @@ pub(super) fn align_and_position_item(
|
||||
|
||||
let (x, x_margin) = align_item_within_area(
|
||||
Line { start: grid_area.left, end: grid_area.right },
|
||||
justify_self.unwrap_or(alignment_styles.horizontal),
|
||||
alignment_styles.horizontal,
|
||||
width,
|
||||
position,
|
||||
inset_horizontal,
|
||||
@@ -300,7 +291,7 @@ pub(super) fn align_and_position_item(
|
||||
);
|
||||
let (y, y_margin) = align_item_within_area(
|
||||
Line { start: grid_area.top, end: grid_area.bottom },
|
||||
align_self.unwrap_or(alignment_styles.vertical),
|
||||
alignment_styles.vertical,
|
||||
height,
|
||||
position,
|
||||
inset_vertical,
|
||||
@@ -388,7 +379,8 @@ pub(super) fn align_item_within_area(
|
||||
// Compute offset in the axis
|
||||
let alignment_based_offset = match alignment_keyword {
|
||||
// TODO: Add support for baseline alignment. For now we treat it as "start".
|
||||
AlignItemsKeyword::Start
|
||||
AlignItemsKeyword::Normal
|
||||
| AlignItemsKeyword::Start
|
||||
| AlignItemsKeyword::FlexStart
|
||||
| AlignItemsKeyword::Baseline
|
||||
| AlignItemsKeyword::Stretch => {
|
||||
@@ -444,6 +436,7 @@ pub(super) fn align_item_within_area(
|
||||
mod tests {
|
||||
use super::{f32_max, f32_min};
|
||||
use crate::prelude::*;
|
||||
use crate::Point;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct IntrinsicInlineSize {
|
||||
@@ -467,6 +460,46 @@ mod tests {
|
||||
Size { width, height: known_dimensions.height.unwrap_or(10.0) }
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct ReplacedIntrinsicSize {
|
||||
width: f32,
|
||||
height: f32,
|
||||
}
|
||||
|
||||
fn measure_replaced_child(
|
||||
known_dimensions: Size<Option<f32>>,
|
||||
_available_space: Size<AvailableSpace>,
|
||||
_node: NodeId,
|
||||
context: Option<&mut ReplacedIntrinsicSize>,
|
||||
_style: &Style,
|
||||
) -> Size<f32> {
|
||||
let intrinsic = context.copied().unwrap_or(ReplacedIntrinsicSize { width: 0.0, height: 0.0 });
|
||||
let ratio = intrinsic.width / intrinsic.height;
|
||||
match known_dimensions {
|
||||
Size { width: Some(width), height: Some(height) } => Size { width, height },
|
||||
Size { width: Some(width), height: None } => Size { width, height: width / ratio },
|
||||
Size { width: None, height: Some(height) } => Size { width: height * ratio, height },
|
||||
Size { width: None, height: None } => Size { width: intrinsic.width, height: intrinsic.height },
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_replaced_grid_item(mut item_style: Style, mut root_style: Style) -> (Point<f32>, Size<f32>) {
|
||||
let mut tree = TaffyTree::new();
|
||||
tree.disable_rounding();
|
||||
item_style.aspect_ratio = Some(2.0);
|
||||
let item = tree
|
||||
.new_leaf_with_context(item_style, ReplacedIntrinsicSize { width: 100.0, height: 50.0 })
|
||||
.unwrap();
|
||||
root_style.display = Display::Grid;
|
||||
root_style.size = Size { width: length(300.0), height: length(200.0) };
|
||||
root_style.grid_template_columns = vec![length(300.0)];
|
||||
root_style.grid_template_rows = vec![length(200.0)];
|
||||
let root = tree.new_with_children(root_style, &[item]).unwrap();
|
||||
tree.compute_layout_with_measure(root, Size::MAX_CONTENT, measure_replaced_child).unwrap();
|
||||
let layout = tree.layout(item).unwrap();
|
||||
(layout.location, layout.size)
|
||||
}
|
||||
|
||||
fn inline_margins(left_auto: bool, right_auto: bool) -> Rect<LengthPercentageAuto> {
|
||||
Rect {
|
||||
left: if left_auto { auto() } else { zero() },
|
||||
@@ -626,6 +659,130 @@ mod tests {
|
||||
assert_eq!(content_box_max, 290.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grid_normal_uses_natural_replaced_size_but_stretches_ordinary_items() {
|
||||
let (_, replaced) = layout_replaced_grid_item(
|
||||
Style { item_is_replaced: true, ..Style::default() },
|
||||
Style::default(),
|
||||
);
|
||||
assert_eq!(replaced, Size { width: 100.0, height: 50.0 });
|
||||
|
||||
let (_, ordinary) = layout_replaced_grid_item(Style::default(), Style::default());
|
||||
assert_eq!(ordinary, Size { width: 300.0, height: 150.0 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ordinary_grid_aspect_ratio_preserves_normal_alignment_provenance() {
|
||||
let cases = [
|
||||
(None, None, None, None, Size { width: 300.0, height: 150.0 }),
|
||||
(None, Some(AlignSelf::STRETCH), None, None, Size { width: 400.0, height: 200.0 }),
|
||||
(Some(AlignSelf::STRETCH), None, None, None, Size { width: 300.0, height: 150.0 }),
|
||||
(
|
||||
Some(AlignSelf::STRETCH),
|
||||
Some(AlignSelf::STRETCH),
|
||||
None,
|
||||
None,
|
||||
Size { width: 300.0, height: 200.0 },
|
||||
),
|
||||
(None, Some(AlignSelf::START), None, None, Size { width: 300.0, height: 150.0 }),
|
||||
(Some(AlignSelf::START), None, None, None, Size { width: 400.0, height: 200.0 }),
|
||||
(None, None, Some(AlignItems::STRETCH), None, Size { width: 400.0, height: 200.0 }),
|
||||
(None, None, None, Some(AlignItems::STRETCH), Size { width: 300.0, height: 150.0 }),
|
||||
];
|
||||
for (justify_self, align_self, align_items, justify_items, expected) in cases {
|
||||
let (_, actual) = layout_replaced_grid_item(
|
||||
Style { justify_self, align_self, ..Style::default() },
|
||||
Style { align_items, justify_items, ..Style::default() },
|
||||
);
|
||||
assert_eq!(actual, expected, "justify={justify_self:?}, align={align_self:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaced_grid_normal_applies_percentage_minimums_without_implicit_stretch() {
|
||||
let (_, inline_min) = layout_replaced_grid_item(
|
||||
Style {
|
||||
item_is_replaced: true,
|
||||
min_size: Size { width: percent(0.5), height: Dimension::auto() },
|
||||
..Style::default()
|
||||
},
|
||||
Style::default(),
|
||||
);
|
||||
assert_eq!(inline_min, Size { width: 150.0, height: 75.0 });
|
||||
|
||||
let (_, block_min) = layout_replaced_grid_item(
|
||||
Style {
|
||||
item_is_replaced: true,
|
||||
min_size: Size { width: Dimension::auto(), height: percent(0.5) },
|
||||
..Style::default()
|
||||
},
|
||||
Style::default(),
|
||||
);
|
||||
assert_eq!(block_min, Size { width: 200.0, height: 100.0 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaced_grid_explicit_stretch_precedes_aspect_ratio_transfer() {
|
||||
let base = Style { item_is_replaced: true, ..Style::default() };
|
||||
|
||||
let (_, inline_stretch) = layout_replaced_grid_item(
|
||||
Style { justify_self: Some(AlignSelf::STRETCH), ..base.clone() },
|
||||
Style::default(),
|
||||
);
|
||||
assert_eq!(inline_stretch, Size { width: 300.0, height: 150.0 });
|
||||
|
||||
let (_, block_stretch) = layout_replaced_grid_item(
|
||||
Style { align_self: Some(AlignSelf::STRETCH), ..base.clone() },
|
||||
Style::default(),
|
||||
);
|
||||
assert_eq!(block_stretch, Size { width: 400.0, height: 200.0 });
|
||||
|
||||
let (_, both_stretch) = layout_replaced_grid_item(
|
||||
Style {
|
||||
justify_self: Some(AlignSelf::STRETCH),
|
||||
align_self: Some(AlignSelf::STRETCH),
|
||||
..base.clone()
|
||||
},
|
||||
Style::default(),
|
||||
);
|
||||
assert_eq!(both_stretch, Size { width: 300.0, height: 200.0 });
|
||||
|
||||
let (_, definite_inline) = layout_replaced_grid_item(
|
||||
Style {
|
||||
size: Size { width: length(120.0), height: Dimension::auto() },
|
||||
align_self: Some(AlignSelf::STRETCH),
|
||||
..base.clone()
|
||||
},
|
||||
Style::default(),
|
||||
);
|
||||
assert_eq!(definite_inline, Size { width: 120.0, height: 200.0 });
|
||||
|
||||
let (_, definite_block) = layout_replaced_grid_item(
|
||||
Style {
|
||||
size: Size { width: Dimension::auto(), height: length(80.0) },
|
||||
justify_self: Some(AlignSelf::STRETCH),
|
||||
..base
|
||||
},
|
||||
Style::default(),
|
||||
);
|
||||
assert_eq!(definite_block, Size { width: 300.0, height: 80.0 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaced_grid_normal_and_auto_margins_remain_natural() {
|
||||
let (location, size) = layout_replaced_grid_item(
|
||||
Style {
|
||||
item_is_replaced: true,
|
||||
min_size: Size { width: percent(0.5), height: Dimension::auto() },
|
||||
margin: Rect { left: auto(), right: zero(), top: zero(), bottom: zero() },
|
||||
..Style::default()
|
||||
},
|
||||
Style { justify_items: Some(AlignItems::NORMAL), ..Style::default() },
|
||||
);
|
||||
assert_eq!(size, Size { width: 150.0, height: 75.0 });
|
||||
assert_eq!(location.x, 150.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overflowing_auto_margins_override_unsafe_self_alignment() {
|
||||
let area = Line { start: 0.0, end: 300.0 };
|
||||
|
||||
Vendored
+76
-2
@@ -34,6 +34,80 @@ mod track_sizing;
|
||||
mod types;
|
||||
mod util;
|
||||
|
||||
/// Resolve grid item `normal` alignment without losing the authored/default
|
||||
/// provenance needed by preferred-aspect-ratio sizing. With an aspect ratio,
|
||||
/// one implicit stretch axis supplies the other; an explicit alignment in the
|
||||
/// opposite axis decides which implicit axis remains stretchable.
|
||||
#[inline]
|
||||
fn resolve_item_alignment(
|
||||
horizontal: AlignSelf,
|
||||
vertical: AlignSelf,
|
||||
is_compressible_replaced: bool,
|
||||
has_preferred_aspect_ratio: bool,
|
||||
) -> InBothAbsAxis<AlignSelf> {
|
||||
if is_compressible_replaced {
|
||||
return InBothAbsAxis {
|
||||
horizontal: horizontal.resolve_normal(AlignSelf::START),
|
||||
vertical: vertical.resolve_normal(AlignSelf::START),
|
||||
};
|
||||
}
|
||||
if !has_preferred_aspect_ratio {
|
||||
return InBothAbsAxis {
|
||||
horizontal: horizontal.resolve_normal(AlignSelf::STRETCH),
|
||||
vertical: vertical.resolve_normal(AlignSelf::STRETCH),
|
||||
};
|
||||
}
|
||||
|
||||
let horizontal_is_normal = horizontal == AlignSelf::NORMAL;
|
||||
let vertical_is_normal = vertical == AlignSelf::NORMAL;
|
||||
InBothAbsAxis {
|
||||
horizontal: if horizontal_is_normal {
|
||||
if !vertical_is_normal && vertical == AlignSelf::STRETCH {
|
||||
AlignSelf::START
|
||||
} else {
|
||||
AlignSelf::STRETCH
|
||||
}
|
||||
} else {
|
||||
horizontal
|
||||
},
|
||||
vertical: if vertical_is_normal {
|
||||
if !horizontal_is_normal && horizontal != AlignSelf::STRETCH {
|
||||
AlignSelf::STRETCH
|
||||
} else {
|
||||
AlignSelf::START
|
||||
}
|
||||
} else {
|
||||
vertical
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn apply_preferred_aspect_ratio(
|
||||
size: Size<Option<f32>>,
|
||||
aspect_ratio: Option<f32>,
|
||||
box_sizing_adjustment: Size<f32>,
|
||||
) -> Size<Option<f32>> {
|
||||
let Some(aspect_ratio) = aspect_ratio else { return size };
|
||||
match size {
|
||||
Size { width: Some(width), height: None } => Size {
|
||||
width: Some(width),
|
||||
height: Some(
|
||||
f32_max(width - box_sizing_adjustment.width, 0.0) / aspect_ratio
|
||||
+ box_sizing_adjustment.height,
|
||||
),
|
||||
},
|
||||
Size { width: None, height: Some(height) } => Size {
|
||||
width: Some(
|
||||
f32_max(height - box_sizing_adjustment.height, 0.0) * aspect_ratio
|
||||
+ box_sizing_adjustment.width,
|
||||
),
|
||||
height: Some(height),
|
||||
},
|
||||
_ => size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Grid layout algorithm
|
||||
/// This consists of a few phases:
|
||||
/// - Resolving the explicit grid
|
||||
@@ -225,8 +299,8 @@ pub fn compute_grid_layout<Tree: LayoutGridContainer>(
|
||||
in_flow_children_iter,
|
||||
direction,
|
||||
style.grid_auto_flow(),
|
||||
align_items.unwrap_or(AlignItems::STRETCH),
|
||||
justify_items.unwrap_or(AlignItems::STRETCH),
|
||||
align_items.unwrap_or(AlignItems::NORMAL),
|
||||
justify_items.unwrap_or(AlignItems::NORMAL),
|
||||
&name_resolver,
|
||||
);
|
||||
|
||||
|
||||
+30
-11
@@ -45,6 +45,8 @@ pub(in super::super) struct GridItem {
|
||||
pub max_size: Size<Dimension>,
|
||||
/// The item's aspect_ratio style
|
||||
pub aspect_ratio: Option<f32>,
|
||||
/// Intrinsic ratios apply to the content box even under border-box sizing.
|
||||
pub aspect_ratio_uses_content_box: bool,
|
||||
/// The item's padding style
|
||||
pub padding: Rect<LengthPercentage>,
|
||||
/// The item's border style
|
||||
@@ -104,12 +106,13 @@ impl GridItem {
|
||||
parent_justify_items: AlignItems,
|
||||
source_order: u16,
|
||||
) -> Self {
|
||||
let is_compressible_replaced = style.is_compressible_replaced();
|
||||
GridItem {
|
||||
node,
|
||||
source_order,
|
||||
row: row_span,
|
||||
column: col_span,
|
||||
is_compressible_replaced: style.is_compressible_replaced(),
|
||||
is_compressible_replaced,
|
||||
intrinsic_size_containment: style.intrinsic_size_containment(),
|
||||
overflow: style.overflow(),
|
||||
box_sizing: style.box_sizing(),
|
||||
@@ -117,6 +120,7 @@ impl GridItem {
|
||||
min_size: style.min_size(),
|
||||
max_size: style.max_size(),
|
||||
aspect_ratio: style.aspect_ratio(),
|
||||
aspect_ratio_uses_content_box: style.aspect_ratio_uses_content_box(),
|
||||
padding: style.padding(),
|
||||
border: style.border(),
|
||||
margin: style.margin(),
|
||||
@@ -263,10 +267,14 @@ impl GridItem {
|
||||
let padding_border_size = (padding + border).sum_axes();
|
||||
let box_sizing_adjustment =
|
||||
if self.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
|
||||
let aspect_ratio_adjustment = if self.aspect_ratio_uses_content_box {
|
||||
padding_border_size
|
||||
} else {
|
||||
box_sizing_adjustment
|
||||
};
|
||||
let inherent_size = self
|
||||
.size
|
||||
.maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
|
||||
.maybe_apply_aspect_ratio(aspect_ratio)
|
||||
.maybe_add(box_sizing_adjustment);
|
||||
let min_size = self
|
||||
.min_size
|
||||
@@ -280,6 +288,12 @@ impl GridItem {
|
||||
.maybe_add(box_sizing_adjustment);
|
||||
|
||||
let grid_area_minus_item_margins_size = grid_area_size.maybe_sub(margins);
|
||||
let alignment = super::super::resolve_item_alignment(
|
||||
self.justify_self,
|
||||
self.align_self,
|
||||
self.is_compressible_replaced,
|
||||
aspect_ratio.is_some(),
|
||||
);
|
||||
|
||||
// If node is absolutely positioned and width is not set explicitly, then deduce it
|
||||
// from left, right and container_content_box if both are set.
|
||||
@@ -288,29 +302,34 @@ impl GridItem {
|
||||
// - Alignment style is "stretch"
|
||||
// - The node is not absolutely positioned
|
||||
// - The node does not have auto margins in this axis.
|
||||
if !self.margin.left.is_auto() && !self.margin.right.is_auto() && self.justify_self == AlignSelf::STRETCH {
|
||||
if !self.margin.left.is_auto()
|
||||
&& !self.margin.right.is_auto()
|
||||
&& alignment.horizontal == AlignSelf::STRETCH
|
||||
{
|
||||
return grid_area_minus_item_margins_size.width;
|
||||
}
|
||||
|
||||
None
|
||||
});
|
||||
// Reapply aspect ratio after stretch and absolute position width adjustments
|
||||
let Size { width, height } =
|
||||
Size { width, height: inherent_size.height }.maybe_apply_aspect_ratio(aspect_ratio);
|
||||
|
||||
let height = height.or_else(|| {
|
||||
let height = inherent_size.height.or_else(|| {
|
||||
// Apply height based on stretch alignment if:
|
||||
// - Alignment style is "stretch"
|
||||
// - The node is not absolutely positioned
|
||||
// - The node does not have auto margins in this axis.
|
||||
if !self.margin.top.is_auto() && !self.margin.bottom.is_auto() && self.align_self == AlignSelf::STRETCH {
|
||||
if !self.margin.top.is_auto()
|
||||
&& !self.margin.bottom.is_auto()
|
||||
&& alignment.vertical == AlignSelf::STRETCH
|
||||
{
|
||||
return grid_area_minus_item_margins_size.height;
|
||||
}
|
||||
|
||||
None
|
||||
});
|
||||
// Reapply aspect ratio after stretch and absolute position height adjustments
|
||||
let Size { width, height } = Size { width, height }.maybe_apply_aspect_ratio(aspect_ratio);
|
||||
let Size { width, height } = super::super::apply_preferred_aspect_ratio(
|
||||
Size { width, height },
|
||||
aspect_ratio,
|
||||
aspect_ratio_adjustment,
|
||||
);
|
||||
|
||||
// Clamp size by min and max width/height
|
||||
let Size { width, height } = Size { width, height }.maybe_clamp(min_size, max_size);
|
||||
|
||||
Vendored
+14
@@ -20,6 +20,8 @@ use crate::util::parse::{CssParseResult, FromCss, Parser, Token};
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[repr(u8)]
|
||||
pub enum AlignItemsKeyword {
|
||||
/// Use the layout mode's normal alignment behavior.
|
||||
Normal,
|
||||
/// Items are packed toward the start of the axis.
|
||||
Start,
|
||||
/// Items are packed toward the end of the axis.
|
||||
@@ -131,6 +133,8 @@ pub struct AlignItems {
|
||||
}
|
||||
|
||||
impl AlignItems {
|
||||
/// Use the layout mode's normal alignment behavior.
|
||||
pub const NORMAL: Self = Self { keyword: AlignItemsKeyword::Normal, safety: AlignmentSafety::Unsafe };
|
||||
/// Items are packed toward the start of the axis.
|
||||
pub const START: Self = Self { keyword: AlignItemsKeyword::Start, safety: AlignmentSafety::Unsafe };
|
||||
/// Items are packed toward the end of the axis.
|
||||
@@ -172,6 +176,12 @@ impl AlignItems {
|
||||
pub const fn keyword(self) -> AlignItemsKeyword {
|
||||
self.keyword
|
||||
}
|
||||
|
||||
/// Resolve the context-dependent `normal` keyword to its used alignment.
|
||||
#[inline]
|
||||
pub const fn resolve_normal(self, normal: Self) -> Self {
|
||||
if matches!(self.keyword, AlignItemsKeyword::Normal) { normal } else { self }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "parse")]
|
||||
@@ -201,6 +211,7 @@ impl FromCss for AlignItems {
|
||||
_ => Err(input.new_unexpected_token_error(Token::Ident(pos))),
|
||||
}
|
||||
},
|
||||
"normal" => Ok(Self::NORMAL),
|
||||
"start" => Ok(Self::START),
|
||||
"end" => Ok(Self::END),
|
||||
"flex-start" => Ok(Self::FLEX_START),
|
||||
@@ -362,6 +373,7 @@ pub type JustifyContent = AlignContent;
|
||||
/// `unknown_variant` errors. Mirrors the spellings produced by `Serialize`.
|
||||
#[cfg(feature = "serde")]
|
||||
const ALIGN_ITEMS_NAMES: &[&str] = &[
|
||||
"Normal",
|
||||
"Start",
|
||||
"End",
|
||||
"FlexStart",
|
||||
@@ -380,6 +392,7 @@ const ALIGN_ITEMS_NAMES: &[&str] = &[
|
||||
impl serde::Serialize for AlignItems {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let name = match (self.keyword, self.safety) {
|
||||
(AlignItemsKeyword::Normal, _) => "Normal",
|
||||
(AlignItemsKeyword::Start, AlignmentSafety::Unsafe) => "Start",
|
||||
(AlignItemsKeyword::End, AlignmentSafety::Unsafe) => "End",
|
||||
(AlignItemsKeyword::FlexStart, AlignmentSafety::Unsafe) => "FlexStart",
|
||||
@@ -408,6 +421,7 @@ impl<'de> serde::Deserialize<'de> for AlignItems {
|
||||
}
|
||||
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
|
||||
Ok(match v {
|
||||
"Normal" => AlignItems::NORMAL,
|
||||
"Start" => AlignItems::START,
|
||||
"End" => AlignItems::END,
|
||||
"FlexStart" => AlignItems::FLEX_START,
|
||||
|
||||
Vendored
+18
@@ -99,6 +99,12 @@ pub trait CoreStyle {
|
||||
fn is_compressible_replaced(&self) -> bool {
|
||||
false
|
||||
}
|
||||
/// Whether the preferred aspect ratio is intrinsic and therefore always
|
||||
/// applies to the content box, independently of `box-sizing`.
|
||||
#[inline(always)]
|
||||
fn aspect_ratio_uses_content_box(&self) -> bool {
|
||||
false
|
||||
}
|
||||
/// Which box do size styles apply to
|
||||
#[inline(always)]
|
||||
fn box_sizing(&self) -> BoxSizing {
|
||||
@@ -442,6 +448,8 @@ pub struct Style<S: CheapCloneStr = DefaultCheapStr> {
|
||||
/// Is it a replaced element like an image or form field?
|
||||
/// <https://drafts.csswg.org/css-sizing-3/#min-content-zero>
|
||||
pub item_is_replaced: bool,
|
||||
/// Whether `aspect_ratio` came from intrinsic media metadata.
|
||||
pub item_aspect_ratio_is_intrinsic: bool,
|
||||
/// Whether descendants are ignored for intrinsic size contributions in each axis.
|
||||
///
|
||||
/// Ordinary final layout is unaffected, allowing an auto-sized grid item to
|
||||
@@ -596,6 +604,7 @@ impl<S: CheapCloneStr> Style<S> {
|
||||
display: Display::DEFAULT,
|
||||
item_is_table: false,
|
||||
item_is_replaced: false,
|
||||
item_aspect_ratio_is_intrinsic: false,
|
||||
intrinsic_size_containment: Size { width: false, height: false },
|
||||
box_sizing: BoxSizing::BorderBox,
|
||||
direction: Direction::Ltr,
|
||||
@@ -693,6 +702,10 @@ impl<S: CheapCloneStr> CoreStyle for Style<S> {
|
||||
self.item_is_replaced
|
||||
}
|
||||
#[inline(always)]
|
||||
fn aspect_ratio_uses_content_box(&self) -> bool {
|
||||
self.item_aspect_ratio_is_intrinsic
|
||||
}
|
||||
#[inline(always)]
|
||||
fn box_sizing(&self) -> BoxSizing {
|
||||
self.box_sizing
|
||||
}
|
||||
@@ -762,6 +775,10 @@ impl<T: CoreStyle> CoreStyle for &'_ T {
|
||||
(*self).is_compressible_replaced()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn aspect_ratio_uses_content_box(&self) -> bool {
|
||||
(*self).aspect_ratio_uses_content_box()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn box_sizing(&self) -> BoxSizing {
|
||||
(*self).box_sizing()
|
||||
}
|
||||
@@ -1222,6 +1239,7 @@ mod tests {
|
||||
display: Default::default(),
|
||||
item_is_table: false,
|
||||
item_is_replaced: false,
|
||||
item_aspect_ratio_is_intrinsic: false,
|
||||
intrinsic_size_containment: Size { width: false, height: false },
|
||||
box_sizing: Default::default(),
|
||||
#[cfg(feature = "float_layout")]
|
||||
|
||||
Reference in new issue
Block a user