diff --git a/Source/HtmlRenderer/Core/Dom/CssBox.cs b/Source/HtmlRenderer/Core/Dom/CssBox.cs
index fbb830828..9c28f4e10 100644
--- a/Source/HtmlRenderer/Core/Dom/CssBox.cs
+++ b/Source/HtmlRenderer/Core/Dom/CssBox.cs
@@ -811,6 +811,7 @@ protected virtual void PerformLayoutImp(RGraphics g)
// Shrink-to-fit, CSS 2.1 §10.3.5 and §10.3.7. GetMinMaxWidth already includes the
// padding and border.
double minWidth, maxWidth;
+ MeasureDescendantWordsSize(g);
GetMinMaxWidth(out minWidth, out maxWidth);
width = Math.Min(Math.Max(minWidth, width), maxWidth);
}
@@ -1111,6 +1112,18 @@ internal virtual void MeasureWordsSize(RGraphics g)
}
}
+ ///
+ /// Measures the words of all descendants, which needs before their own layout ran.
+ ///
+ internal void MeasureDescendantWordsSize(RGraphics g)
+ {
+ foreach (var childBox in Boxes)
+ {
+ childBox.MeasureWordsSize(g);
+ childBox.MeasureDescendantWordsSize(g);
+ }
+ }
+
///
/// Get the parent of this css properties instance.
///
diff --git a/Source/HtmlRenderer/Core/Dom/CssLayoutEngineTable.cs b/Source/HtmlRenderer/Core/Dom/CssLayoutEngineTable.cs
index 7ec074be1..c654f43c9 100644
--- a/Source/HtmlRenderer/Core/Dom/CssLayoutEngineTable.cs
+++ b/Source/HtmlRenderer/Core/Dom/CssLayoutEngineTable.cs
@@ -141,7 +141,7 @@ public static void PerformLayout(RGraphics g, CssBox tableBox)
///
private void Layout(RGraphics g)
{
- MeasureWords(_tableBox, g);
+ _tableBox.MeasureDescendantWordsSize(g);
// get the table boxes into the proper fields
AssignBoxKinds();
@@ -890,23 +890,6 @@ private static bool RowHasCellSpanningIntoSubsequentRow(CssBox row, int currentr
return false;
}
- ///
- /// Recursively measures words inside the box
- ///
- /// the box to measure
- /// Device to use
- private static void MeasureWords(CssBox box, RGraphics g)
- {
- if (box != null)
- {
- foreach (var childBox in box.Boxes)
- {
- childBox.MeasureWordsSize(g);
- MeasureWords(childBox, g);
- }
- }
- }
-
///
/// Tells if the columns widths can be reduced,
/// by checking the minimum widths of all cells
diff --git a/Source/Test/HtmlRenderer.IntegrationTest/BoxModel/WidthIntegrationTests.cs b/Source/Test/HtmlRenderer.IntegrationTest/BoxModel/WidthIntegrationTests.cs
index 20d72ccc3..22222e32b 100644
--- a/Source/Test/HtmlRenderer.IntegrationTest/BoxModel/WidthIntegrationTests.cs
+++ b/Source/Test/HtmlRenderer.IntegrationTest/BoxModel/WidthIntegrationTests.cs
@@ -195,7 +195,6 @@ public void Float_WithAutoMargins_HasZeroMargins()
Assert.AreEqual(50, f.Size.Width, Delta);
}
- [Ignore("Gap, see Spec/css21.yml")]
[TestMethod]
[Spec("css21", "visudet.html#float-width")]
[Spec("css-sizing-3", "#intrinsic-sizes")]
@@ -210,6 +209,29 @@ public void AutoWidthFloat_ShrinksToFitItsContent()
Assert.AreEqual(words[0].Top, words[^1].Top, Delta);
}
+ [TestMethod]
+ [Spec("css21", "visudet.html#float-width")]
+ public void AutoWidthFloat_IsSizedByTheFirstLayout()
+ {
+ var (root, container) = LayoutHarness.Layout(LayoutHarness.Wrap(
+ "" +
+ "
8.6
" +
+ "
line one
line two
"), maxWidth: 400);
+ var f = Box(root, "f");
+ var words = LayoutHarness.Descendants(f).SelectMany(b => b.Words).ToList();
+ var size = container.ActualSize;
+
+ Assert.AreEqual(words[^1].Right - words[0].Left, f.Size.Width, 1);
+ Assert.AreEqual(1, f.Location.X, Delta);
+ Assert.AreEqual(f.ActualRight + 24, FirstWord(Box(root, "p"))!.Left, 1);
+
+ // Another layout pass must not change anything
+ LayoutHarness.PerformLayout(container);
+
+ Assert.AreEqual(size.Width, container.ActualSize.Width, Delta);
+ Assert.AreEqual(size.Height, container.ActualSize.Height, Delta);
+ }
+
[TestMethod]
[Spec("css21", "visudet.html#float-replaced-width")]
public void FloatedImage_KeepsItsIntrinsicWidth()
@@ -255,7 +277,6 @@ public void Absolute_WithLeftAndRight_StretchesTheAutoWidth()
Assert.AreEqual(160, a.Size.Width, Delta);
}
- [Ignore("Gap, see Spec/css21.yml")]
[TestMethod]
[Spec("css21", "visudet.html#abs-non-replaced-width")]
[Spec("css-position-3", "#abs-non-replaced-width")]
diff --git a/Source/Test/HtmlRenderer.IntegrationTest/TestSupport/LayoutHarness.cs b/Source/Test/HtmlRenderer.IntegrationTest/TestSupport/LayoutHarness.cs
index f5c2c0b45..74ab3fd81 100644
--- a/Source/Test/HtmlRenderer.IntegrationTest/TestSupport/LayoutHarness.cs
+++ b/Source/Test/HtmlRenderer.IntegrationTest/TestSupport/LayoutHarness.cs
@@ -57,16 +57,22 @@ internal static (CssBox Root, HtmlContainerInt Container) Layout(
prepare(container.Root!);
}
- using var bitmap = new Bitmap(1, 1, PixelFormat.Format32bppArgb);
- using var g = Graphics.FromImage(bitmap);
- using var graphics = new GraphicsAdapter(g, false);
- container.PerformLayout(graphics);
+ PerformLayout(container);
Assert.IsNotNull(container.Root);
return (container.Root!, container);
}
+ /// Runs one layout pass of , e.g. a second one after .
+ internal static void PerformLayout(HtmlContainerInt container)
+ {
+ using var bitmap = new Bitmap(1, 1, PixelFormat.Format32bppArgb);
+ using var g = Graphics.FromImage(bitmap);
+ using var graphics = new GraphicsAdapter(g, false);
+ container.PerformLayout(graphics);
+ }
+
/// Wraps a body fragment in a minimal document, so a test can state only the markup it cares about.
internal static string Wrap(string body) => $"{body}";
diff --git a/Spec/css-position-3.yml b/Spec/css-position-3.yml
index 47f4025d5..8df041b5b 100644
--- a/Spec/css-position-3.yml
+++ b/Spec/css-position-3.yml
@@ -81,7 +81,6 @@ sections:
status: partial
gaps:
- With both insets set, an auto size is its content size instead of the stretch-fit size
- - The fit-content width is computed before the content is measured, see CSS 2.1 10.3.7
'#abspos-margins':
title: 4.2 Auto Margins of Absolutely-Positioned Boxes
status: partial
diff --git a/Spec/css-sizing-3.yml b/Spec/css-sizing-3.yml
index afc52669c..722679842 100644
--- a/Spec/css-sizing-3.yml
+++ b/Spec/css-sizing-3.yml
@@ -74,7 +74,6 @@ sections:
title: 5.1 Intrinsic Sizes
status: partial
gaps:
- - The fit-content width of an auto width float is computed before its content is measured, see CSS 2.1 10.3.5
- An auto width inline-block wider than the line wraps across lines instead of shrinking, see CSS 2.1 10.3.9
'#intrinsic-contribution':
title: 5.2 Intrinsic Contributions
diff --git a/Spec/css-sizing-4.yml b/Spec/css-sizing-4.yml
index 791a968b7..19961c670 100644
--- a/Spec/css-sizing-4.yml
+++ b/Spec/css-sizing-4.yml
@@ -50,7 +50,7 @@ sections:
title: 5.1 Intrinsic Sizes
status: partial
gaps:
- - Floats and inline-blocks aren't sized to their content as specified, see css-sizing-3 5.1
+ - Inline-blocks aren't sized to their content as specified, see css-sizing-3 5.1
'#intrinsic-size-override':
title: '5.2 Overriding Contained Intrinsic Sizes: the contain-intrinsic-* properties'
status: parsed-only
diff --git a/Spec/css21.yml b/Spec/css21.yml
index 6da038eb2..573a22f2e 100644
--- a/Spec/css21.yml
+++ b/Spec/css21.yml
@@ -503,9 +503,7 @@ sections:
- Auto margins compute to 0, a block image is never centered by them
visudet.html#float-width:
title: 10.3.5 Floating, non-replaced elements
- status: partial
- gaps:
- - The shrink-to-fit width of an auto width float is computed before its content is measured, on the first layout it's 0 or NaN and the text wraps after every word
+ status: supported
visudet.html#float-replaced-width:
title: 10.3.6 Floating, replaced elements
status: supported
@@ -514,7 +512,6 @@ sections:
status: partial
gaps:
- With 'left' and 'right' set, an auto width is 0 instead of filling the space between them
- - The shrink-to-fit width of an auto width box is computed before its content is measured, on the first layout it's 0 or NaN
- With 'left', 'right' and 'width' set, auto margins compute to 0 instead of centering the box
- With 'left' and 'right' auto, the box is placed at the containing block's content edge instead of its static position
visudet.html#abs-replaced-width: