Commit 085cf007 authored by glebl's avatar glebl Committed by Commit bot

[LayoutNG] Fix broken layout while using CSS with writing modes.

1) This will set a writing mode to the fragment builder so it can be used at the time when we calculate a physical fragment.
2) Change computeMargin to accept 2 more parameters: Direction and writing mode.

TEST=NGBlockLayoutAlgorithmTest.LayoutBlockChildrenWithWritingMode
BUG=635619

Review-Url: https://codereview.chromium.org/2347773002
Cr-Commit-Position: refs/heads/master@{#419309}
parent 14947f52
......@@ -53,6 +53,8 @@ bool NGBlockLayoutAlgorithm::Layout(const NGConstraintSpace* constraint_space,
content_size_ = border_and_padding_.block_start;
builder_ = new NGFragmentBuilder(NGPhysicalFragmentBase::FragmentBox);
builder_->SetDirection(constraint_space->Direction());
builder_->SetWritingMode(constraint_space->WritingMode());
builder_->SetInlineSize(inline_size).SetBlockSize(block_size);
current_child_ = first_child_;
state_ = kStateChildLayout;
......@@ -64,7 +66,9 @@ bool NGBlockLayoutAlgorithm::Layout(const NGConstraintSpace* constraint_space,
if (!current_child_->Layout(constraint_space_for_children_, &fragment))
return false;
NGBoxStrut child_margins = computeMargins(
*constraint_space_for_children_, *current_child_->Style());
*constraint_space_for_children_, *current_child_->Style(),
constraint_space_for_children_->WritingMode(),
constraint_space_for_children_->Direction());
LayoutUnit margin_block_start = CollapseMargins(
*constraint_space, child_margins, fragment->MarginStrut());
......
......@@ -35,8 +35,9 @@ TEST_F(NGBlockLayoutAlgorithmTest, FixedSize) {
style_->setWidth(Length(30, Fixed));
style_->setHeight(Length(40, Fixed));
auto* space = new NGConstraintSpace(
HorizontalTopBottom, NGLogicalSize(LayoutUnit(100), NGSizeIndefinite));
auto* space =
new NGConstraintSpace(HorizontalTopBottom, LeftToRight,
NGLogicalSize(LayoutUnit(100), NGSizeIndefinite));
NGPhysicalFragment* frag = RunBlockLayoutAlgorithm(space, nullptr);
EXPECT_EQ(frag->Width(), LayoutUnit(30));
......@@ -64,8 +65,9 @@ TEST_F(NGBlockLayoutAlgorithmTest, LayoutBlockChildren) {
first_child->SetNextSibling(second_child);
auto* space = new NGConstraintSpace(
HorizontalTopBottom, NGLogicalSize(LayoutUnit(100), NGSizeIndefinite));
auto* space =
new NGConstraintSpace(HorizontalTopBottom, LeftToRight,
NGLogicalSize(LayoutUnit(100), NGSizeIndefinite));
NGPhysicalFragment* frag = RunBlockLayoutAlgorithm(space, first_child);
EXPECT_EQ(frag->Width(), LayoutUnit(kWidth));
......@@ -82,6 +84,47 @@ TEST_F(NGBlockLayoutAlgorithmTest, LayoutBlockChildren) {
EXPECT_EQ(child->TopOffset(), kHeight1 + kMarginTop);
}
// Verifies that a child is laid out correctly if it's writing mode is different
// from the parent's one.
//
// Test case's HTML representation:
// <div style="writing-mode: vertical-lr;">
// <div style="width:50px;
// height: 50px; margin-left: 100px;
// writing-mode: horizontal-tb;"></div>
// </div>
TEST_F(NGBlockLayoutAlgorithmTest, LayoutBlockChildrenWithWritingMode) {
const int kWidth = 50;
const int kHeight = 50;
const int kMarginLeft = 100;
RefPtr<ComputedStyle> div1_style = ComputedStyle::create();
div1_style->setWritingMode(LeftToRightWritingMode);
NGBox* div1 = new NGBox(div1_style.get());
RefPtr<ComputedStyle> div2_style = ComputedStyle::create();
div2_style->setHeight(Length(kHeight, Fixed));
div2_style->setWidth(Length(kWidth, Fixed));
div1_style->setWritingMode(TopToBottomWritingMode);
div2_style->setMarginLeft(Length(kMarginLeft, Fixed));
NGBox* div2 = new NGBox(div2_style.get());
div1->SetFirstChild(div2);
auto* space =
new NGConstraintSpace(HorizontalTopBottom, LeftToRight,
NGLogicalSize(LayoutUnit(500), LayoutUnit(500)));
NGPhysicalFragment* frag = RunBlockLayoutAlgorithm(space, div1);
const NGPhysicalFragmentBase* child = frag->Children()[0];
// DIV2
child = static_cast<const NGPhysicalFragment*>(child)->Children()[0];
EXPECT_EQ(kHeight, child->Height());
EXPECT_EQ(0, child->TopOffset());
EXPECT_EQ(kMarginLeft, child->LeftOffset());
}
// Verifies the collapsing margins case for the next pair:
// - top margin of a box and top margin of its first in-flow child.
//
......@@ -110,8 +153,9 @@ TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase1) {
div1->SetFirstChild(div2);
auto* space = new NGConstraintSpace(
HorizontalTopBottom, NGLogicalSize(LayoutUnit(100), NGSizeIndefinite));
auto* space =
new NGConstraintSpace(HorizontalTopBottom, LeftToRight,
NGLogicalSize(LayoutUnit(100), NGSizeIndefinite));
NGPhysicalFragment* frag = RunBlockLayoutAlgorithm(space, div1);
EXPECT_EQ(frag->MarginStrut(), NGMarginStrut({LayoutUnit(kDiv1MarginTop)}));
......@@ -170,8 +214,9 @@ TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase2) {
div3->SetFirstChild(div4);
div1->SetNextSibling(div3);
auto* space = new NGConstraintSpace(
HorizontalTopBottom, NGLogicalSize(LayoutUnit(100), NGSizeIndefinite));
auto* space =
new NGConstraintSpace(HorizontalTopBottom, LeftToRight,
NGLogicalSize(LayoutUnit(100), NGSizeIndefinite));
NGPhysicalFragment* frag = RunBlockLayoutAlgorithm(space, div1);
ASSERT_EQ(frag->Children().size(), 2UL);
......@@ -216,8 +261,9 @@ TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase3) {
div1->SetFirstChild(div2);
auto* space = new NGConstraintSpace(
HorizontalTopBottom, NGLogicalSize(LayoutUnit(100), NGSizeIndefinite));
auto* space =
new NGConstraintSpace(HorizontalTopBottom, LeftToRight,
NGLogicalSize(LayoutUnit(100), NGSizeIndefinite));
NGPhysicalFragment* frag = RunBlockLayoutAlgorithm(space, div1);
// Verify that margins are collapsed.
......@@ -264,8 +310,9 @@ TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase4) {
div1->SetFirstChild(div2);
auto* space = new NGConstraintSpace(
HorizontalTopBottom, NGLogicalSize(LayoutUnit(100), NGSizeIndefinite));
auto* space =
new NGConstraintSpace(HorizontalTopBottom, LeftToRight,
NGLogicalSize(LayoutUnit(100), NGSizeIndefinite));
NGPhysicalFragment* frag = RunBlockLayoutAlgorithm(space, div1);
// Verify that margins do NOT collapse.
......@@ -332,8 +379,9 @@ TEST_F(NGBlockLayoutAlgorithmTest, BorderAndPadding) {
div1->SetFirstChild(div2);
auto* space = new NGConstraintSpace(
HorizontalTopBottom, NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite));
auto* space =
new NGConstraintSpace(HorizontalTopBottom, LeftToRight,
NGLogicalSize(LayoutUnit(1000), NGSizeIndefinite));
NGPhysicalFragment* frag = RunBlockLayoutAlgorithm(space, div1);
ASSERT_EQ(frag->Children().size(), 1UL);
......
......@@ -34,7 +34,8 @@ bool NGBox::Layout(const NGConstraintSpace* constraint_space,
algorithm_ = new NGBlockLayoutAlgorithm(Style(), FirstChild());
// Change the coordinate system of the constraint space.
NGConstraintSpace* child_constraint_space = new NGConstraintSpace(
FromPlatformWritingMode(Style()->getWritingMode()), constraint_space);
FromPlatformWritingMode(Style()->getWritingMode()),
FromPlatformDirection(Style()->direction()), constraint_space);
NGPhysicalFragment* fragment = nullptr;
if (!algorithm_->Layout(child_constraint_space, &fragment))
......@@ -83,10 +84,14 @@ bool NGBox::Layout(const NGConstraintSpace* constraint_space,
}
LayoutRect overflow = layout_box_->layoutOverflowRect();
// TODO(layout-ng): This does not handle writing modes correctly (for
// overflow & the enums)
// overflow)
NGFragmentBuilder builder(NGPhysicalFragmentBase::FragmentBox);
builder.SetInlineSize(layout_box_->logicalWidth())
.SetBlockSize(layout_box_->logicalHeight())
.SetDirection(
FromPlatformDirection(layout_box_->styleRef().direction()))
.SetWritingMode(
FromPlatformWritingMode(layout_box_->styleRef().getWritingMode()))
.SetInlineOverflow(overflow.width())
.SetBlockOverflow(overflow.height());
fragment_ = builder.ToFragment();
......
......@@ -15,33 +15,41 @@ namespace blink {
// remove it requiring that a NGConstraintSpace is created from a
// NGPhysicalConstraintSpace.
NGConstraintSpace::NGConstraintSpace(NGWritingMode writing_mode,
NGDirection direction,
NGLogicalSize container_size)
: physical_space_(new NGPhysicalConstraintSpace(
container_size.ConvertToPhysical(writing_mode))),
size_(container_size),
writing_mode_(writing_mode) {}
writing_mode_(writing_mode),
direction_(direction) {}
NGConstraintSpace::NGConstraintSpace(NGWritingMode writing_mode,
NGDirection direction,
NGPhysicalConstraintSpace* physical_space)
: physical_space_(physical_space),
size_(physical_space->ContainerSize().ConvertToLogical(writing_mode)),
writing_mode_(writing_mode) {}
writing_mode_(writing_mode),
direction_(direction) {}
NGConstraintSpace::NGConstraintSpace(NGWritingMode writing_mode,
NGDirection direction,
const NGConstraintSpace* constraint_space)
: physical_space_(constraint_space->PhysicalSpace()),
offset_(constraint_space->Offset()),
size_(constraint_space->Size()),
writing_mode_(writing_mode) {}
writing_mode_(writing_mode),
direction_(direction) {}
NGConstraintSpace::NGConstraintSpace(NGWritingMode writing_mode,
NGDirection direction,
const NGConstraintSpace& other,
NGLogicalOffset offset,
NGLogicalSize size)
: physical_space_(other.PhysicalSpace()),
offset_(offset),
size_(size),
writing_mode_(writing_mode) {}
writing_mode_(writing_mode),
direction_(direction) {}
NGConstraintSpace::NGConstraintSpace(const NGConstraintSpace& other,
NGLogicalOffset offset,
......@@ -49,7 +57,8 @@ NGConstraintSpace::NGConstraintSpace(const NGConstraintSpace& other,
: physical_space_(other.PhysicalSpace()),
offset_(offset),
size_(size),
writing_mode_(other.WritingMode()) {}
writing_mode_(other.WritingMode()),
direction_(other.Direction()) {}
NGConstraintSpace* NGConstraintSpace::CreateFromLayoutObject(
const LayoutBox& box) {
......@@ -75,6 +84,7 @@ NGConstraintSpace* NGConstraintSpace::CreateFromLayoutObject(
NGConstraintSpace* derived_constraint_space = new NGConstraintSpace(
FromPlatformWritingMode(box.styleRef().getWritingMode()),
FromPlatformDirection(box.styleRef().direction()),
NGLogicalSize(container_logical_width, container_logical_height));
derived_constraint_space->SetOverflowTriggersScrollbar(
box.styleRef().overflowInlineDirection() == OverflowAuto,
......
......@@ -26,24 +26,26 @@ class CORE_EXPORT NGConstraintSpace final
: public GarbageCollected<NGConstraintSpace> {
public:
// Constructs a constraint space with a new backing NGPhysicalConstraintSpace.
NGConstraintSpace(NGWritingMode, NGLogicalSize);
NGConstraintSpace(NGWritingMode, NGDirection, NGLogicalSize);
// Constructs a constraint space based on an existing backing
// NGPhysicalConstraintSpace.
NGConstraintSpace(NGWritingMode, NGPhysicalConstraintSpace*);
NGConstraintSpace(NGWritingMode, NGDirection, NGPhysicalConstraintSpace*);
// Constructs a constraint space with a different NGWritingMode.
NGConstraintSpace(NGWritingMode, const NGConstraintSpace*);
// Constructs a constraint space with a different NGWritingMode and
// NGDirection.
NGConstraintSpace(NGWritingMode, NGDirection, const NGConstraintSpace*);
// Constructs a derived constraint space sharing the same backing
// NGPhysicalConstraintSpace and NGWritingMode.
// NGPhysicalConstraintSpace, NGWritingMode and NGDirection.
NGConstraintSpace(const NGConstraintSpace& other,
NGLogicalOffset,
NGLogicalSize);
// Constructs a derived constraint space sharing the same backing
// NGPhysicalConstraintSpace and a different NGWritingMode.
// NGPhysicalConstraintSpace, a different NGWritingMode and NGDirection.
NGConstraintSpace(NGWritingMode,
NGDirection,
const NGConstraintSpace& other,
NGLogicalOffset,
NGLogicalSize);
......@@ -54,6 +56,8 @@ class CORE_EXPORT NGConstraintSpace final
NGPhysicalConstraintSpace* PhysicalSpace() const { return physical_space_; }
NGDirection Direction() const { return static_cast<NGDirection>(direction_); }
NGWritingMode WritingMode() const {
return static_cast<NGWritingMode>(writing_mode_);
}
......@@ -116,6 +120,7 @@ class CORE_EXPORT NGConstraintSpace final
NGLogicalOffset offset_;
NGLogicalSize size_;
unsigned writing_mode_ : 3;
unsigned direction_ : 1;
};
inline std::ostream& operator<<(std::ostream& stream,
......
......@@ -12,14 +12,15 @@ namespace blink {
namespace {
TEST(NGConstraintSpaceTest, WritingMode) {
NGConstraintSpace* horz_space = new NGConstraintSpace(
HorizontalTopBottom, NGLogicalSize(LayoutUnit(200), LayoutUnit(100)));
NGConstraintSpace* horz_space =
new NGConstraintSpace(HorizontalTopBottom, LeftToRight,
NGLogicalSize(LayoutUnit(200), LayoutUnit(100)));
horz_space->SetOverflowTriggersScrollbar(true, false);
horz_space->SetFixedSize(true, false);
horz_space->SetFragmentationType(FragmentColumn);
NGConstraintSpace* vert_space =
new NGConstraintSpace(VerticalRightLeft, horz_space);
new NGConstraintSpace(VerticalRightLeft, LeftToRight, horz_space);
EXPECT_EQ(LayoutUnit(200), horz_space->ContainerSize().inline_size);
EXPECT_EQ(LayoutUnit(200), vert_space->ContainerSize().block_size);
......@@ -48,7 +49,8 @@ TEST(NGConstraintSpaceTest, LayoutOpportunitiesNoExclusions) {
physical_size.width = LayoutUnit(600);
physical_size.height = LayoutUnit(400);
auto* physical_space = new NGPhysicalConstraintSpace(physical_size);
auto* space = new NGConstraintSpace(HorizontalTopBottom, physical_space);
auto* space =
new NGConstraintSpace(HorizontalTopBottom, LeftToRight, physical_space);
bool for_inline_or_bfc = true;
auto* iterator = space->LayoutOpportunities(NGClearNone, for_inline_or_bfc);
......@@ -72,7 +74,8 @@ TEST(NGConstraintSpaceTest, LayoutOpportunitiesOneExclusion) {
physical_space->AddExclusion(NGExclusion(LayoutUnit(0), LayoutUnit(600),
LayoutUnit(100), LayoutUnit(500)));
auto* space = new NGConstraintSpace(HorizontalTopBottom, physical_space);
auto* space =
new NGConstraintSpace(HorizontalTopBottom, LeftToRight, physical_space);
bool for_inline_or_bfc = true;
auto* iterator = space->LayoutOpportunities(NGClearNone, for_inline_or_bfc);
......
......@@ -70,6 +70,7 @@ NGConstraintSpace* NGLayoutOpportunityIterator::Next() {
if (filtered_exclusions_.isEmpty() && current_exclusion_idx_ == 0) {
current_exclusion_idx_++;
return new NGConstraintSpace(constraint_space_->WritingMode(),
constraint_space_->Direction(),
constraint_space_->PhysicalSpace());
}
......
......@@ -14,6 +14,47 @@ namespace blink {
// - positioned and/or replaced calculations
// - Take scrollbars into account
namespace {
// Converts physical dimensions to logical ones per
// https://drafts.csswg.org/css-writing-modes-3/#logical-to-physical
// For now it's only used to calculate abstract values for margins.
NGBoxStrut ToLogicalDimensions(const NGPhysicalDimensions& physical_dim,
const NGWritingMode writing_mode,
const NGDirection direction) {
bool is_ltr = direction == LeftToRight;
NGBoxStrut logical_dim;
switch (writing_mode) {
case VerticalRightLeft:
case SidewaysRightLeft:
logical_dim = {is_ltr ? physical_dim.top : physical_dim.bottom,
is_ltr ? physical_dim.bottom : physical_dim.top,
physical_dim.right, physical_dim.left};
break;
case VerticalLeftRight:
logical_dim = {is_ltr ? physical_dim.top : physical_dim.bottom,
is_ltr ? physical_dim.bottom : physical_dim.top,
physical_dim.left, physical_dim.right};
break;
case SidewaysLeftRight:
logical_dim = {is_ltr ? physical_dim.bottom : physical_dim.top,
is_ltr ? physical_dim.top : physical_dim.bottom,
physical_dim.left, physical_dim.right};
break;
default:
NOTREACHED();
/* FALLTHROUGH */
case HorizontalTopBottom:
logical_dim = {is_ltr ? physical_dim.left : physical_dim.right,
is_ltr ? physical_dim.right : physical_dim.left,
physical_dim.top, physical_dim.bottom};
break;
}
return logical_dim;
}
} // namespace
LayoutUnit resolveInlineLength(const NGConstraintSpace& constraintSpace,
const ComputedStyle& style,
const Length& length,
......@@ -32,7 +73,10 @@ LayoutUnit resolveInlineLength(const NGConstraintSpace& constraintSpace,
switch (length.type()) {
case Auto:
case FillAvailable: {
NGBoxStrut margins = computeMargins(constraintSpace, style);
NGBoxStrut margins =
computeMargins(constraintSpace, style,
FromPlatformWritingMode(style.getWritingMode()),
FromPlatformDirection(style.direction()));
return container_size - margins.InlineSum();
}
case Percent:
......@@ -88,7 +132,10 @@ LayoutUnit resolveBlockLength(const NGConstraintSpace& constraintSpace,
LayoutUnit container_size = constraintSpace.ContainerSize().block_size;
switch (length.type()) {
case FillAvailable: {
NGBoxStrut margins = computeMargins(constraintSpace, style);
NGBoxStrut margins =
computeMargins(constraintSpace, style,
FromPlatformWritingMode(style.getWritingMode()),
FromPlatformDirection(style.direction()));
return container_size - margins.BlockSum();
}
case Percent:
......@@ -178,23 +225,25 @@ LayoutUnit computeBlockSizeForFragment(const NGConstraintSpace& constraintSpace,
}
NGBoxStrut computeMargins(const NGConstraintSpace& constraintSpace,
const ComputedStyle& style) {
const ComputedStyle& style,
const NGWritingMode writing_mode,
const NGDirection direction) {
// Margins always get computed relative to the inline size:
// https://www.w3.org/TR/CSS2/box.html#value-def-margin-width
NGBoxStrut margins;
margins.inline_start =
resolveInlineLength(constraintSpace, style, style.marginStart(),
NGPhysicalDimensions physical_dim;
physical_dim.left =
resolveInlineLength(constraintSpace, style, style.marginLeft(),
LengthResolveType::MarginBorderPaddingSize);
margins.inline_end =
resolveInlineLength(constraintSpace, style, style.marginEnd(),
physical_dim.right =
resolveInlineLength(constraintSpace, style, style.marginRight(),
LengthResolveType::MarginBorderPaddingSize);
margins.block_start =
resolveInlineLength(constraintSpace, style, style.marginBefore(),
physical_dim.top =
resolveInlineLength(constraintSpace, style, style.marginTop(),
LengthResolveType::MarginBorderPaddingSize);
margins.block_end =
resolveInlineLength(constraintSpace, style, style.marginAfter(),
physical_dim.bottom =
resolveInlineLength(constraintSpace, style, style.marginBottom(),
LengthResolveType::MarginBorderPaddingSize);
return margins;
return ToLogicalDimensions(physical_dim, writing_mode, direction);
}
NGBoxStrut computeBorders(const ComputedStyle& style) {
......
......@@ -6,6 +6,8 @@
#define NGLengthUtils_h
#include "core/CoreExport.h"
#include "core/layout/ng/ng_direction.h"
#include "core/layout/ng/ng_writing_mode.h"
namespace blink {
class ComputedStyle;
......@@ -52,7 +54,9 @@ CORE_EXPORT LayoutUnit computeBlockSizeForFragment(const NGConstraintSpace&,
LayoutUnit contentSize);
CORE_EXPORT NGBoxStrut computeMargins(const NGConstraintSpace&,
const ComputedStyle&);
const ComputedStyle&,
const NGWritingMode writing_mode,
const NGDirection direction);
CORE_EXPORT NGBoxStrut computeBorders(const ComputedStyle&);
......
......@@ -25,7 +25,7 @@ class NGLengthUtilsTest : public ::testing::Test {
bool fixed_inline = false,
bool fixed_block = false) {
NGConstraintSpace* derived_constraint_space = new NGConstraintSpace(
HorizontalTopBottom,
HorizontalTopBottom, LeftToRight,
NGLogicalSize(LayoutUnit(inline_size), LayoutUnit(block_size)));
derived_constraint_space->SetOverflowTriggersScrollbar(false, false);
derived_constraint_space->SetFixedSize(fixed_inline, fixed_block);
......@@ -210,7 +210,8 @@ TEST_F(NGLengthUtilsTest, testMargins) {
NGConstraintSpace* constraintSpace(ConstructConstraintSpace(200, 300));
NGBoxStrut margins = computeMargins(*constraintSpace, *style_);
NGBoxStrut margins = computeMargins(*constraintSpace, *style_,
HorizontalTopBottom, LeftToRight);
EXPECT_EQ(LayoutUnit(20), margins.block_start);
EXPECT_EQ(LayoutUnit(52), margins.inline_end);
......
......@@ -89,6 +89,16 @@ struct NGPixelSnappedPhysicalRect {
int height;
};
// Struct to store physical dimensions, independent of writing mode and
// direction.
// See https://drafts.csswg.org/css-writing-modes-3/#abstract-box
struct NGPhysicalDimensions {
LayoutUnit left;
LayoutUnit right;
LayoutUnit top;
LayoutUnit bottom;
};
// This struct is used for storing margins, borders or padding of a box on all
// four edges.
struct NGBoxStrut {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment