Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 194 additions & 12 deletions Source/HtmlRenderer/Core/Dom/CssBox.cs

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion Source/HtmlRenderer/Core/Dom/CssBoxHr.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ protected override void PerformLayoutImp(RGraphics g)

var prevSibling = DomUtils.GetPreviousSibling(this);
double left = ContainingBlock.Location.X + ContainingBlock.ActualPaddingLeft + ActualMarginLeft + ContainingBlock.ActualBorderLeftWidth;
double top = (prevSibling == null && ParentBox != null ? ParentBox.ClientTop : ParentBox == null ? Location.Y : 0) + MarginTopCollapse(prevSibling) + (prevSibling != null ? prevSibling.ActualBottom + prevSibling.ActualBorderBottomWidth : 0);
// StaticBottom (not ActualBottom): a relatively-positioned previous sibling's visual offset must
// not drag this rule down with it (CSS 2.1 9.4.3), matching the same fix in CssBox.PerformLayoutImp.
double top = (prevSibling == null && ParentBox != null ? ParentBox.ClientTop : ParentBox == null ? Location.Y : 0) + MarginTopCollapse(prevSibling) + (prevSibling != null ? prevSibling.StaticBottom + prevSibling.ActualBorderBottomWidth : 0);
Location = new RPoint(left, top);
ActualBottom = top;

Expand Down
49 changes: 37 additions & 12 deletions Source/HtmlRenderer/Core/Dom/CssBoxProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -571,26 +571,20 @@ public string Left
get { return _left; }
set
{
// Deliberately no eager position:fixed recompute here (as this once had): it raced ahead
// of ActualMarginLeft/Top and ContainingBlock/HtmlContainer being ready and cached a
// margin-less Location that never got recomputed once they were. position:fixed placement
// is instead resolved once, in PerformLayoutImp, once the box is fully set up.
_left = value;

if (Position == CssConstants.Fixed)
{
_location = GetActualLocation(Left, Top);
}
}
}

public string Top
{
get { return _top; }
set {
set
{
_top = value;

if (Position == CssConstants.Fixed)
{
_location = GetActualLocation(Left, Top);
}

}
}

Expand Down Expand Up @@ -726,6 +720,37 @@ public string Position
set { _position = value; }
}

public string Right
{
get { return _right; }
set { _right = value; }
}

public string Bottom
{
get { return _bottom; }
set { _bottom = value; }
}

/// <summary>
/// The visual-only offset a <c>position:relative</c> box's placement branch applied, per CSS 2.1
/// §9.4.3 - kept separately so <see cref="StaticBottom"/> can back it back out for margin-collapse/
/// sibling-placement consumers that must lay out against the box's un-offset (static) position.
/// Ported from PeachPDF's CssBox.RelativeOffsetX/Y.
/// </summary>
public double RelativeOffsetX { get; set; }

/// <inheritdoc cref="RelativeOffsetX"/>
public double RelativeOffsetY { get; set; }

/// <summary>
/// <see cref="CssBoxProperties.ActualBottom"/> with any <c>position:relative</c> visual offset
/// backed out - the coordinate a following sibling or this box's own parent (for auto height) must
/// lay out against, since relative positioning "has no effect on the position of any other box"
/// (CSS 2.1 §9.4.3). Ported from PeachPDF's CssBox.StaticBottom.
/// </summary>
public double StaticBottom => ActualBottom - RelativeOffsetY;

public string LineHeight
{
get { return _lineHeight; }
Expand Down
2 changes: 2 additions & 0 deletions Source/HtmlRenderer/Core/Utils/CssConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ internal static class CssConstants
public const string Pre = "pre";
public const string PreWrap = "pre-wrap";
public const string PreLine = "pre-line";
public const string Relative = "relative";
public const string Right = "right";
public const string Rtl = "rtl";
public const string SansSerif = "sans-serif";
Expand All @@ -103,6 +104,7 @@ internal static class CssConstants
public const string Small = "small";
public const string Smaller = "smaller";
public const string Solid = "solid";
public const string Static = "static";
public const string Sub = "sub";
public const string Super = "super";
public const string Square = "square";
Expand Down
12 changes: 11 additions & 1 deletion Source/HtmlRenderer/Core/Utils/CssUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ internal static class CssUtils
"padding-bottom", "padding-left", "padding-right", "padding-top",
"page-break-inside", "break-inside", "break-before", "break-after", "page-break-before", "page-break-after",
"widows", "orphans", "page",
"left", "top", "width", "max-width", "height", "min-height", "max-height",
"left", "top", "right", "bottom", "width", "max-width", "height", "min-height", "max-height",
"background-color", "background-image", "background-position", "background-repeat",
"content", "color", "display", "direction", "empty-cells", "float", "clear", "box-sizing", "position",
"line-height", "vertical-align", "text-indent", "text-align", "text-decoration-line",
Expand Down Expand Up @@ -171,6 +171,10 @@ public static string GetPropertyValue(CssBox cssBox, string propName)
return cssBox.Left;
case "top":
return cssBox.Top;
case "right":
return cssBox.Right;
case "bottom":
return cssBox.Bottom;
case "width":
return cssBox.Width;
case "max-width":
Expand Down Expand Up @@ -376,6 +380,12 @@ public static void SetPropertyValue(CssBox cssBox, string propName, string value
case "top":
cssBox.Top = value;
break;
case "right":
cssBox.Right = value;
break;
case "bottom":
cssBox.Bottom = value;
break;
case "width":
cssBox.Width = value;
break;
Expand Down
18 changes: 18 additions & 0 deletions Source/HtmlRenderer/Core/Utils/DomUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,24 @@ public static bool IsBoxHasWhitespace(CssBox box)
return false;
}

/// <summary>
/// The nearest positioned ancestor (CSS 2.1 §10.1: a box whose <c>position</c> is anything other
/// than <c>static</c>) of <paramref name="box"/>, or the document root if none is found - the
/// containing block a <c>position:absolute</c> box's offsets/percentages resolve against. Ported
/// from PeachPDF's DomUtils.GetNearestPositionedAncestor.
/// </summary>
internal static CssBox GetNearestPositionedAncestor(CssBox box)
{
var current = box.ParentBox;

while (current.ParentBox != null && current.Position == CssConstants.Static)
{
current = current.ParentBox;
}

return current;
}

/// <summary>
/// The candidate rectangle being tested against existing floats, either during float placement
/// (<see cref="CssLayoutEngine"/>'s FloatBoxLeft/FloatBoxRight) or while flowing a line's inline
Expand Down
Binary file modified Source/Test/HtmlRenderer.IntegrationTest/Baselines/Tables.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,10 @@ public sealed class HrPlacementTests
[TestMethod]
public void ARelativelyPositionedPredecessor_DoesNotDragTheRuleWithIt()
{
// Note: position:relative has no implementation anywhere in this fork's Core at all (confirmed - no
// "Relative"/CssConstants.Relative handling exists in Core/Dom/CssBox.cs or CssBoxProperties.cs), so
// 'top' on a relatively-positioned box is simply ignored; the box never moves in the first place. This
// test still genuinely passes - it just does so because relative offsetting is a no-op here, not because
// it's correctly excluded from the flow calculation the way CSS2.1 requires.
// CSS 2.1 §9.4.3: relative positioning is purely visual - the offset must not affect where a
// following sibling lays out. CssBoxHr.PerformLayoutImp reads prevSibling.StaticBottom (which backs
// the offset back out), not ActualBottom, so the rule ends up in the same place whether or not its
// predecessor is relatively positioned.
var (staticRoot, _) = LayoutHarness.Layout(LayoutHarness.Wrap(
"<div id='a' style='height:40px'></div><hr id='h' style='margin:0'>"));
var (offsetRoot, _) = LayoutHarness.Layout(LayoutHarness.Wrap(
Expand Down
Loading
Loading