Commit ac9d3f8a authored by eae's avatar eae Committed by Commit bot

Initial exclusion aware layout opportunities implementation

Implement exclusion aware layout opportunities algorithm. At the moment
it only handles opportunities for a fixed starting point, sorted by the
width of the opportunities, widest first. Next step is to recompute for
the next exclusion once all current opportunities have been exhausted.

BUG=591099
R=ikilpatrick@chromium.org

Review-Url: https://codereview.chromium.org/2298273002
Cr-Commit-Position: refs/heads/master@{#417562}
parent 7aad3cae
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
#include "core/layout/LayoutBlock.h" #include "core/layout/LayoutBlock.h"
#include "core/layout/LayoutView.h" #include "core/layout/LayoutView.h"
#include "core/layout/ng/ng_units.h" #include "core/layout/ng/ng_units.h"
#include "wtf/NonCopyingSort.h"
#include <climits>
namespace blink { namespace blink {
...@@ -120,10 +122,11 @@ void NGConstraintSpace::Subtract(const NGFragment*) { ...@@ -120,10 +122,11 @@ void NGConstraintSpace::Subtract(const NGFragment*) {
// TODO(layout-ng): Implement. // TODO(layout-ng): Implement.
} }
NGLayoutOpportunityIterator NGConstraintSpace::LayoutOpportunities( NGLayoutOpportunityIterator* NGConstraintSpace::LayoutOpportunities(
unsigned clear, unsigned clear,
bool for_inline_or_bfc) { bool for_inline_or_bfc) {
NGLayoutOpportunityIterator iterator(this, clear, for_inline_or_bfc); NGLayoutOpportunityIterator* iterator =
new NGLayoutOpportunityIterator(this, clear, for_inline_or_bfc);
return iterator; return iterator;
} }
...@@ -168,12 +171,139 @@ String NGConstraintSpace::toString() const { ...@@ -168,12 +171,139 @@ String NGConstraintSpace::toString() const {
size_.block_size.toString().ascii().data()); size_.block_size.toString().ascii().data());
} }
static inline bool AscendingTopCompare(const NGExclusion& a,
const NGExclusion& b) {
return a.Top() > b.Top();
}
NGLayoutOpportunityIterator::NGLayoutOpportunityIterator(
NGConstraintSpace* space,
unsigned clear,
bool for_inline_or_bfc)
: constraint_space_(space),
clear_(clear),
for_inline_or_bfc_(for_inline_or_bfc),
current_exclusion_idx_(0) {
for (const auto& item : constraint_space_->PhysicalSpace()->Exclusions())
filtered_exclusions_.append(item);
nonCopyingSort(filtered_exclusions_.begin(), filtered_exclusions_.end(),
AscendingTopCompare);
// TODO(eae): Set based on offset.
LayoutUnit left;
LayoutUnit top;
unsigned i = filtered_exclusions_.size();
while (i--) {
const NGExclusion& exclusion = filtered_exclusions_[i];
// Remove items above OR to the left of the start offset as they have no
// effect on layout opportunities within this view.
if (exclusion.Right() <= left || exclusion.Bottom() <= top) {
filtered_exclusions_.remove(i);
continue;
}
// Remove items below AND to the right of the current exclusions as they're
// occluded and won't affect the layout opportunities.
for (unsigned j = filtered_exclusions_.size() - 1; j > i; j--) {
const NGExclusion& item = filtered_exclusions_[j];
if (item.Top() > exclusion.Top() && item.Left() > exclusion.Left())
filtered_exclusions_.remove(j);
}
}
}
NGConstraintSpace* NGLayoutOpportunityIterator::Next() { NGConstraintSpace* NGLayoutOpportunityIterator::Next() {
auto* exclusions = constraint_space_->PhysicalSpace()->Exclusions(); if (current_opportunities_.isEmpty() &&
if (!exclusions->head()) current_exclusion_idx_ < filtered_exclusions_.size()) {
computeForExclusion(current_exclusion_idx_);
current_exclusion_idx_++;
}
if (!current_opportunities_.isEmpty()) {
NGConstraintSpace* opportunity = current_opportunities_.last();
current_opportunities_.removeLast();
return opportunity;
}
if (filtered_exclusions_.isEmpty() && current_exclusion_idx_ == 0) {
current_exclusion_idx_++;
return new NGConstraintSpace(constraint_space_->WritingMode(), return new NGConstraintSpace(constraint_space_->WritingMode(),
constraint_space_->PhysicalSpace()); constraint_space_->PhysicalSpace());
}
return nullptr; return nullptr;
} }
static inline bool DescendingWidthCompare(const NGConstraintSpace* a,
const NGConstraintSpace* b) {
return a->Size().inline_size > b->Size().inline_size;
}
void NGLayoutOpportunityIterator::computeForExclusion(unsigned index) {
current_opportunities_.clear();
// TODO(eae): Set based on index.
LayoutUnit left;
LayoutUnit top;
// TODO(eae): Writing modes.
LayoutUnit right = constraint_space_->Size().inline_size;
LayoutUnit bottom = constraint_space_->Size().block_size;
// TODO(eae): Filter based on clear_ and for_inline_or_bfc_. Return early for
// now to make it clear neither are supported yet.
if (clear_ != NGClearNone || !for_inline_or_bfc_)
return;
// Compute opportunity for the full width from the start position to the right
// edge of the NGConstraintSpace.
LayoutUnit opportunityHeight = heightForOpportunity(left, top, right, bottom);
if (opportunityHeight && right > left)
addLayoutOpportunity(left, top, right - left, opportunityHeight);
// Compute the maximum available height between the current position and the
// left edge of each exclusion. The distance between the current horizontal
// position and the left edge of the exclusion determines the width of the
// opportunity.
for (const NGExclusion& exclusion : filtered_exclusions_) {
opportunityHeight =
heightForOpportunity(left, top, exclusion.Left(), bottom);
if (opportunityHeight && exclusion.Left() > left)
addLayoutOpportunity(left, top, exclusion.Left() - left,
opportunityHeight);
}
nonCopyingSort(current_opportunities_.begin(), current_opportunities_.end(),
DescendingWidthCompare);
}
// For the given 2D range (opportunity), this will return a height which makes
// it bounded by the highest exclusion in the filtered exclusion list within the
// range. Returns 0-height for an invalid opportunity (which has zero area).
LayoutUnit NGLayoutOpportunityIterator::heightForOpportunity(
LayoutUnit left,
LayoutUnit top,
LayoutUnit right,
LayoutUnit bottom) {
LayoutUnit lowestBottom = bottom;
for (const NGExclusion& exclusion : filtered_exclusions_) {
if (exclusion.Left() < right && exclusion.Right() > left &&
exclusion.Bottom() > top && exclusion.Top() <= lowestBottom)
lowestBottom = exclusion.Top();
}
return std::max(lowestBottom - top, LayoutUnit());
}
void NGLayoutOpportunityIterator::addLayoutOpportunity(LayoutUnit left,
LayoutUnit top,
LayoutUnit right,
LayoutUnit bottom) {
current_opportunities_.append(
new NGConstraintSpace(*constraint_space_, NGLogicalOffset(left, top),
NGLogicalSize(right - left, bottom - top)));
}
} // namespace blink } // namespace blink
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "core/layout/ng/ng_writing_mode.h" #include "core/layout/ng/ng_writing_mode.h"
#include "platform/heap/Handle.h" #include "platform/heap/Handle.h"
#include "wtf/text/WTFString.h" #include "wtf/text/WTFString.h"
#include "wtf/Vector.h"
namespace blink { namespace blink {
...@@ -98,7 +99,7 @@ class CORE_EXPORT NGConstraintSpace final ...@@ -98,7 +99,7 @@ class CORE_EXPORT NGConstraintSpace final
// size, or add an exclusion. // size, or add an exclusion.
void Subtract(const NGFragment*); void Subtract(const NGFragment*);
NGLayoutOpportunityIterator LayoutOpportunities( NGLayoutOpportunityIterator* LayoutOpportunities(
unsigned clear = NGClearNone, unsigned clear = NGClearNone,
bool for_inline_or_bfc = false); bool for_inline_or_bfc = false);
...@@ -119,22 +120,38 @@ class CORE_EXPORT NGConstraintSpace final ...@@ -119,22 +120,38 @@ class CORE_EXPORT NGConstraintSpace final
unsigned writing_mode_ : 3; unsigned writing_mode_ : 3;
}; };
class CORE_EXPORT NGLayoutOpportunityIterator final { class CORE_EXPORT NGLayoutOpportunityIterator final
: public GarbageCollectedFinalized<NGLayoutOpportunityIterator> {
public: public:
NGLayoutOpportunityIterator(NGConstraintSpace* space, NGLayoutOpportunityIterator(NGConstraintSpace* space,
unsigned clear, unsigned clear,
bool for_inline_or_bfc) bool for_inline_or_bfc);
: constraint_space_(space),
clear_(clear),
for_inline_or_bfc_(for_inline_or_bfc) {}
~NGLayoutOpportunityIterator() {} ~NGLayoutOpportunityIterator() {}
NGConstraintSpace* Next(); NGConstraintSpace* Next();
DEFINE_INLINE_VIRTUAL_TRACE() {
visitor->trace(constraint_space_);
visitor->trace(current_opportunities_);
}
private: private:
Persistent<NGConstraintSpace> constraint_space_; void computeForExclusion(unsigned index);
LayoutUnit heightForOpportunity(LayoutUnit left,
LayoutUnit top,
LayoutUnit right,
LayoutUnit bottom);
void addLayoutOpportunity(LayoutUnit left,
LayoutUnit top,
LayoutUnit right,
LayoutUnit bottom);
Member<NGConstraintSpace> constraint_space_;
unsigned clear_; unsigned clear_;
bool for_inline_or_bfc_; bool for_inline_or_bfc_;
Vector<NGExclusion> filtered_exclusions_;
HeapVector<Member<NGConstraintSpace>> current_opportunities_;
unsigned current_exclusion_idx_;
}; };
inline std::ostream& operator<<(std::ostream& stream, inline std::ostream& operator<<(std::ostream& stream,
......
...@@ -42,8 +42,49 @@ TEST(NGConstraintSpaceTest, WritingMode) { ...@@ -42,8 +42,49 @@ TEST(NGConstraintSpaceTest, WritingMode) {
EXPECT_EQ(FragmentNone, vert_space->BlockFragmentationType()); EXPECT_EQ(FragmentNone, vert_space->BlockFragmentationType());
} }
TEST(NGConstraintSpaceTest, LayoutOpportunities) { TEST(NGConstraintSpaceTest, LayoutOpportunitiesNoExclusions) {
// TODO(eae): Implement in followup change. NGPhysicalSize physical_size;
physical_size.width = LayoutUnit(600);
physical_size.height = LayoutUnit(400);
auto* physical_space = new NGPhysicalConstraintSpace(physical_size);
auto* space = new NGConstraintSpace(HorizontalTopBottom, physical_space);
bool for_inline_or_bfc = true;
auto* iterator = space->LayoutOpportunities(NGClearNone, for_inline_or_bfc);
const NGConstraintSpace* firstOpportunity = iterator->Next();
EXPECT_NE(nullptr, firstOpportunity);
EXPECT_EQ(LayoutUnit(600), firstOpportunity->Size().inline_size);
EXPECT_EQ(LayoutUnit(400), firstOpportunity->Size().block_size);
const NGConstraintSpace* secondOpportunity = iterator->Next();
EXPECT_EQ(nullptr, secondOpportunity);
}
TEST(NGConstraintSpaceTest, LayoutOpportunitiesOneExclusion) {
NGPhysicalSize physical_size;
physical_size.width = LayoutUnit(600);
physical_size.height = LayoutUnit(400);
auto* physical_space = new NGPhysicalConstraintSpace(physical_size);
// Add a 100x100 exclusion in the top right corner.
physical_space->AddExclusion(NGExclusion(LayoutUnit(0), LayoutUnit(600),
LayoutUnit(100), LayoutUnit(500)));
auto* space = new NGConstraintSpace(HorizontalTopBottom, physical_space);
bool for_inline_or_bfc = true;
auto* iterator = space->LayoutOpportunities(NGClearNone, for_inline_or_bfc);
// First opportunity should be to the left of the exclusion.
const NGConstraintSpace* firstOpportunity = iterator->Next();
EXPECT_NE(nullptr, firstOpportunity);
EXPECT_EQ(LayoutUnit(0), firstOpportunity->Offset().inline_offset);
EXPECT_EQ(LayoutUnit(0), firstOpportunity->Offset().block_offset);
EXPECT_EQ(LayoutUnit(500), firstOpportunity->Size().inline_size);
EXPECT_EQ(LayoutUnit(400), firstOpportunity->Size().block_size);
const NGConstraintSpace* secondOpportunity = iterator->Next();
EXPECT_EQ(nullptr, secondOpportunity);
} }
} // namespace } // namespace
......
...@@ -40,16 +40,16 @@ NGPhysicalConstraintSpace::NGPhysicalConstraintSpace( ...@@ -40,16 +40,16 @@ NGPhysicalConstraintSpace::NGPhysicalConstraintSpace(
height_direction_fragmentation_type_( height_direction_fragmentation_type_(
other.height_direction_fragmentation_type_) {} other.height_direction_fragmentation_type_) {}
void NGPhysicalConstraintSpace::AddExclusion(const NGExclusion, void NGPhysicalConstraintSpace::AddExclusion(const NGExclusion exclusion,
unsigned options) { unsigned options) {
// TODO(layout-ng): Implement. exclusions_.append(exclusion);
} }
const DoublyLinkedList<const NGExclusion>* const Vector<NGExclusion>& NGPhysicalConstraintSpace::Exclusions(
NGPhysicalConstraintSpace::Exclusions(unsigned options) const { unsigned options) const {
// TODO(layout-ng): Filter based on options? Perhaps layout Opportunities // TODO(layout-ng): Filter based on options? Perhaps layout Opportunities
// should filter instead? // should filter instead?
return &exclusions_; return exclusions_;
} }
} // namespace blink } // namespace blink
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
#include "core/CoreExport.h" #include "core/CoreExport.h"
#include "core/layout/ng/ng_units.h" #include "core/layout/ng/ng_units.h"
#include "platform/heap/Handle.h" #include "platform/heap/Handle.h"
#include "wtf/DoublyLinkedList.h" #include "wtf/Vector.h"
#include "wtf/text/WTFString.h"
namespace blink { namespace blink {
...@@ -26,17 +27,35 @@ enum NGFragmentationType { ...@@ -26,17 +27,35 @@ enum NGFragmentationType {
FragmentRegion FragmentRegion
}; };
class NGExclusion { struct NGExclusion {
public: NGExclusion(LayoutUnit top,
NGExclusion(); LayoutUnit right,
~NGExclusion() {} LayoutUnit bottom,
LayoutUnit left) {
rect.location.left = left;
rect.location.top = top;
rect.size.width = right - left;
rect.size.height = bottom - top;
}
LayoutUnit Top() const { return rect.location.top; }
LayoutUnit Right() const { return rect.size.width + rect.location.left; }
LayoutUnit Bottom() const { return rect.size.height + rect.location.top; }
LayoutUnit Left() const { return rect.location.left; }
String toString() const {
return String::format(
"Exclusion: %0.2f, %0.2f, size: %0.2f, %0.2f (right %0.2f)",
rect.location.left.toFloat(), rect.location.top.toFloat(),
rect.size.width.toFloat(), rect.size.height.toFloat(),
Right().toFloat());
}
NGPhysicalRect rect;
}; };
// The NGPhysicalConstraintSpace contains the underlying data for the // The NGPhysicalConstraintSpace contains the underlying data for the
// NGConstraintSpace. It is not meant to be used directly as all members are in // NGConstraintSpace. It is not meant to be used directly as all members are in
// the physical coordinate space. Instead NGConstraintSpace should be used. // the physical coordinate space. Instead NGConstraintSpace should be used.
class CORE_EXPORT NGPhysicalConstraintSpace final class CORE_EXPORT NGPhysicalConstraintSpace final
: public GarbageCollected<NGPhysicalConstraintSpace> { : public GarbageCollectedFinalized<NGPhysicalConstraintSpace> {
public: public:
NGPhysicalConstraintSpace(); NGPhysicalConstraintSpace();
NGPhysicalConstraintSpace(NGPhysicalSize); NGPhysicalConstraintSpace(NGPhysicalSize);
...@@ -45,8 +64,7 @@ class CORE_EXPORT NGPhysicalConstraintSpace final ...@@ -45,8 +64,7 @@ class CORE_EXPORT NGPhysicalConstraintSpace final
NGPhysicalSize ContainerSize() const { return container_size_; } NGPhysicalSize ContainerSize() const { return container_size_; }
void AddExclusion(const NGExclusion, unsigned options = 0); void AddExclusion(const NGExclusion, unsigned options = 0);
const DoublyLinkedList<const NGExclusion>* Exclusions( const Vector<NGExclusion>& Exclusions(unsigned options = 0) const;
unsigned options = 0) const;
DEFINE_INLINE_TRACE() {} DEFINE_INLINE_TRACE() {}
...@@ -62,7 +80,7 @@ class CORE_EXPORT NGPhysicalConstraintSpace final ...@@ -62,7 +80,7 @@ class CORE_EXPORT NGPhysicalConstraintSpace final
unsigned width_direction_fragmentation_type_ : 2; unsigned width_direction_fragmentation_type_ : 2;
unsigned height_direction_fragmentation_type_ : 2; unsigned height_direction_fragmentation_type_ : 2;
DoublyLinkedList<const NGExclusion> exclusions_; Vector<NGExclusion> exclusions_;
}; };
} // namespace blink } // namespace blink
......
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