diff --git a/Source/HtmlRenderer/Core/Dom/CssBox.cs b/Source/HtmlRenderer/Core/Dom/CssBox.cs index 1ef899648..d84d32590 100644 --- a/Source/HtmlRenderer/Core/Dom/CssBox.cs +++ b/Source/HtmlRenderer/Core/Dom/CssBox.cs @@ -238,7 +238,7 @@ public bool IsPseudoElement /// public bool IsInline { - get { return (Display == CssConstants.Inline || Display == CssConstants.InlineBlock) && !IsBrElement; } + get { return Display == CssConstants.Inline || Display == CssConstants.InlineBlock; } } /// @@ -1474,9 +1474,10 @@ internal void GetMinMaxWidth(out double minWidth, out double maxWidth) double maxSum = 0f; double paddingSum = 0f; double marginSum = 0f; - GetMinMaxSumWords(this, ref min, ref maxSum, ref paddingSum, ref marginSum); + double brokenLinesMax = 0f; + GetMinMaxSumWords(this, ref min, ref maxSum, ref paddingSum, ref marginSum, ref brokenLinesMax); - maxWidth = paddingSum + maxSum; + maxWidth = paddingSum + Math.Max(maxSum, brokenLinesMax); minWidth = paddingSum + (min < 90999 ? min : 0); } @@ -1488,10 +1489,12 @@ internal void GetMinMaxWidth(out double minWidth, out double maxWidth) /// the max width a single line of words can take without wrapping /// the total amount of padding the content has /// + /// the widest line already ended by a line break in the current block /// - private static void GetMinMaxSumWords(CssBox box, ref double min, ref double maxSum, ref double paddingSum, ref double marginSum) + private static void GetMinMaxSumWords(CssBox box, ref double min, ref double maxSum, ref double paddingSum, ref double marginSum, ref double brokenLinesMax) { double? oldSum = null; + double oldBrokenLinesMax = 0; // paddingSum must be scoped per "line" the same way maxSum is (see the oldSum save/restore // below) - it represents the border/padding belonging to the WIDEST line found so far, not a @@ -1509,6 +1512,8 @@ private static void GetMinMaxSumWords(CssBox box, ref double min, ref double max maxSum = marginSum; oldPaddingSum = paddingSum; paddingSum = 0; + oldBrokenLinesMax = brokenLinesMax; + brokenLinesMax = 0; } // add the padding @@ -1524,6 +1529,14 @@ private static void GetMinMaxSumWords(CssBox box, ref double min, ref double max // calculate the min and max sum for all the words in the box foreach (CssRect word in box.Words) { + // A line break ends the line, so the next word starts a new one instead of adding to it. + if (word.IsLineBreak) + { + brokenLinesMax = Math.Max(brokenLinesMax, maxSum); + maxSum = marginSum; + continue; + } + maxSum += word.FullWidth + (word.HasSpaceBefore ? word.OwnerBox.ActualWordSpacing : 0); min = Math.Max(min, word.Width); } @@ -1542,7 +1555,7 @@ private static void GetMinMaxSumWords(CssBox box, ref double min, ref double max //maxSum += childBox.ActualMarginLeft + childBox.ActualMarginRight; var maxSumBeforeChild = maxSum; - GetMinMaxSumWords(childBox, ref min, ref maxSum, ref paddingSum, ref marginSum); + GetMinMaxSumWords(childBox, ref min, ref maxSum, ref paddingSum, ref marginSum, ref brokenLinesMax); // This walk otherwise never consults a box's own explicit CSS `width` at all - only // literal word/text content. That's usually fine (explicit width constrains layout @@ -1575,7 +1588,8 @@ private static void GetMinMaxSumWords(CssBox box, ref double min, ref double max // max sum (and its matching padding contribution) is the max of all the lines in the box if (oldSum.HasValue) { - maxSum = Math.Max(maxSum, oldSum.Value); + maxSum = Math.Max(Math.Max(maxSum, brokenLinesMax), oldSum.Value); + brokenLinesMax = oldBrokenLinesMax; paddingSum = Math.Max(paddingSum, oldPaddingSum!.Value); } } diff --git a/Source/HtmlRenderer/Core/Dom/CssLayoutEngine.cs b/Source/HtmlRenderer/Core/Dom/CssLayoutEngine.cs index c1ebb75d9..6acf2a0be 100644 --- a/Source/HtmlRenderer/Core/Dom/CssLayoutEngine.cs +++ b/Source/HtmlRenderer/Core/Dom/CssLayoutEngine.cs @@ -467,9 +467,14 @@ private static void FlowBox(RGraphics g, CssBox blockbox, CssBox box, double lim actualLimitRight = rightObstructingFloat.Location.X - rightObstructingFloat.ActualMarginLeft - rightspacing; } + // A line break ends the current line, so the word after it starts the next one. Wrapping on + // the break itself would add an empty line after a trailing
and drop the one a + // leading
creates. + var afterLineBreak = line.Words.Count > 0 && line.Words[line.Words.Count - 1].IsLineBreak; + if ((b.WhiteSpace != CssConstants.NoWrap && b.WhiteSpace != CssConstants.Pre && curx + word.Width + rightspacing > actualLimitRight && (b.WhiteSpace != CssConstants.PreWrap || !word.IsSpaces)) - || word.IsLineBreak || wrapNoWrapBox) + || afterLineBreak || wrapNoWrapBox) { wrapNoWrapBox = false; curx = startx; diff --git a/Source/HtmlRenderer/Core/Parse/HtmlParser.cs b/Source/HtmlRenderer/Core/Parse/HtmlParser.cs index 3e85e1997..534ace396 100644 --- a/Source/HtmlRenderer/Core/Parse/HtmlParser.cs +++ b/Source/HtmlRenderer/Core/Parse/HtmlParser.cs @@ -98,11 +98,29 @@ private static void AddTextBox(HtmlDataToken token, ref CssBox curBox) if (text == null) return; + // HTML drops a single newline directly after the
,  or