Commit a9f390c2 authored by Morten Stenshorne's avatar Morten Stenshorne Committed by Commit Bot

[LayoutNG] Support fragmentainer breaks between column spanners.

While it's nice on its own to handle this, this CL can also be used as
an example to lean on when implementing block fragmentation support in
other layout modes, such as flexbox and tables.

We still don't support early breaks before column rows. That may also be
a useful thing to support, but it would require some more work, since
we'd need another way of specifying early breaks, because column rows
aren't uniquely identified by nodes. NGEarlyBreak currently only
supports breaks before line numbers or nodes.

Bug: 829028
Change-Id: Id1517790826223402c6ea4cd72ca565e0bd0e368
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1865325
Commit-Queue: Morten Stenshorne <mstensho@chromium.org>
Reviewed-by: default avatarIan Kilpatrick <ikilpatrick@chromium.org>
Cr-Commit-Position: refs/heads/master@{#707261}
parent 1fbbcc1d
......@@ -233,24 +233,8 @@ class CORE_EXPORT NGBlockLayoutAlgorithm
// returned.
bool FinalizeForFragmentation();
// Insert a fragmentainer break before the child if necessary. In that case,
// the previous in-flow position will be updated, we'll return |kBrokeBefore|.
// If we don't break inside, we'll consider the appeal of doing so anyway (and
// store it as the most appealing break point so far if that's the case),
// since we might have to go back and break here. Return |kContinue| if we're
// to continue laying out. If |kNeedsEarlierBreak| is returned, it means that
// we ran out of space, but shouldn't break before the child, but rather abort
// layout, and re-layout to a previously found good breakpoint. If
// |has_container_separation| is true, it means that we're at a valid
// breakpoint. We obviously prefer valid breakpoints, but sometimes we need to
// break at undesirable locations. Class A breakpoints occur between block
// siblings. Class B breakpoints between line boxes. Both these breakpoint
// classes imply that we're already past the first in-flow child in the
// container, but there's also another way of achieving container separation:
// class C breakpoints. Those occur if there's a positive gap between the
// block-start content edge of the container and the block-start margin edge
// of the first in-flow child. This can happen when in-flow content is pushed
// down by floats. https://www.w3.org/TR/css-break-3/#possible-breaks
// Insert a fragmentainer break before the child if necessary.
// See |::blink::BreakBeforeChildIfNeeded()| for more documentation.
NGBreakStatus BreakBeforeChildIfNeeded(NGLayoutInputNode child,
const NGLayoutResult&,
NGPreviousInflowPosition*,
......
......@@ -200,6 +200,10 @@ class CORE_EXPORT NGBoxFragmentBuilder final
break_appeal_ = appeal;
}
bool HasEarlyBreak() const { return early_break_.get(); }
const NGEarlyBreak& EarlyBreak() const {
DCHECK(early_break_.get());
return *early_break_.get();
}
// Set the highest break appeal found so far. This is either:
// 1: The highest appeal of a breakpoint found by our container
......
......@@ -10,6 +10,7 @@
namespace blink {
enum class NGBreakStatus;
class NGBlockNode;
class NGBlockBreakToken;
class NGConstraintSpace;
......@@ -29,8 +30,10 @@ class CORE_EXPORT NGColumnLayoutAlgorithm
const MinMaxSizeInput&) const override;
private:
// Lay out as many children as we can.
void LayoutChildren();
// Lay out as many children as we can. If false is returned, it means that we
// ran out of space at an unappealing location, and need to relayout and break
// earlier (because we have a better breakpoint there).
bool LayoutChildren();
// Lay out one row of columns. The layout result returned is for the last
// column that was laid out. The rows themselves don't create fragments.
......@@ -38,13 +41,16 @@ class CORE_EXPORT NGColumnLayoutAlgorithm
const NGBlockBreakToken* next_column_token,
NGMarginStrut*);
// Lay out a column spanner. Will return a break token if we break before or
// inside the spanner. If no break token is returned, it means that we can
// proceed to the next row of columns.
scoped_refptr<const NGBlockBreakToken> LayoutSpanner(
NGBlockNode spanner_node,
const NGBlockBreakToken* break_token,
NGMarginStrut*);
// Lay out a column spanner. The return value will tell whether to break
// before the spanner or not. If we're not to break before the spanner, but
// rather inside, |spanner_break_token| will be set, so that we know where to
// resume in the next outer fragmentainer. If |NGBreakStatus::kContinue| is
// returned, and no break token was set, it means that we can proceed to the
// next row of columns.
NGBreakStatus LayoutSpanner(NGBlockNode spanner_node,
const NGBlockBreakToken* break_token,
NGMarginStrut*,
scoped_refptr<const NGBlockBreakToken>*);
LayoutUnit CalculateBalancedColumnBlockSize(
const LogicalSize& column_size,
......@@ -58,6 +64,16 @@ class CORE_EXPORT NGColumnLayoutAlgorithm
LayoutUnit ConstrainColumnBlockSize(LayoutUnit size) const;
LayoutUnit CurrentContentBlockOffset() const;
// Finalize layout after breaking before a spanner.
void FinishAfterBreakBeforeSpanner(
scoped_refptr<const NGBlockBreakToken> next_column_token);
// Lay out again, this time with a predefined good breakpoint that we
// discovered in the first pass. This happens when we run out of space in a
// fragmentainer at an less-than-ideal location, due to breaking restrictions,
// such as break-before:avoid or break-after:avoid.
scoped_refptr<const NGLayoutResult> RelayoutAndBreakEarlier();
NGConstraintSpace CreateConstraintSpaceForColumns(
const LogicalSize& column_size,
bool is_first_fragmentainer,
......@@ -68,6 +84,9 @@ class CORE_EXPORT NGColumnLayoutAlgorithm
LayoutUnit block_offset) const;
NGConstraintSpace CreateConstraintSpaceForMinMax() const;
// When set, this will specify where to break before or inside.
const NGEarlyBreak* early_break_ = nullptr;
const NGBoxStrut border_padding_;
const NGBoxStrut border_scrollbar_padding_;
LogicalSize content_box_size_;
......@@ -76,6 +95,11 @@ class CORE_EXPORT NGColumnLayoutAlgorithm
LayoutUnit column_inline_progression_;
LayoutUnit intrinsic_block_size_;
bool is_constrained_by_outer_fragmentation_context_ = false;
// This will be set during (outer) block fragmentation once we've processed
// the first piece of content of the multicol container. It is used to check
// if we're at a valid class A breakpoint (between block-level siblings).
bool has_processed_first_child_ = false;
};
} // namespace blink
......
......@@ -213,6 +213,44 @@ void FinishFragmentation(const NGConstraintSpace& space,
builder->SetIntrinsicBlockSize(intrinsic_block_size);
}
NGBreakStatus BreakBeforeChildIfNeeded(const NGConstraintSpace& space,
NGLayoutInputNode child,
const NGLayoutResult& layout_result,
LayoutUnit fragmentainer_block_offset,
bool has_container_separation,
NGBoxFragmentBuilder* builder) {
DCHECK(space.HasBlockFragmentation());
if (has_container_separation) {
EBreakBetween break_between =
CalculateBreakBetweenValue(child, layout_result, *builder);
if (IsForcedBreakValue(space, break_between)) {
BreakBeforeChild(space, child, layout_result, fragmentainer_block_offset,
kBreakAppealPerfect, /* is_forced_break */ true,
builder);
return NGBreakStatus::kBrokeBefore;
}
}
NGBreakAppeal appeal_before = CalculateBreakAppealBefore(
space, child, layout_result, *builder, has_container_separation);
// Attempt to move past the break point, and if we can do that, also assess
// the appeal of breaking there, even if we didn't.
if (MovePastBreakpoint(space, child, layout_result,
fragmentainer_block_offset, appeal_before, builder))
return NGBreakStatus::kContinue;
// Breaking inside the child isn't appealing, and we're out of space. Figure
// out where to insert a soft break. It will either be before this child, or
// before an earlier sibling, if there's a more appealing breakpoint there.
if (!AttemptSoftBreak(space, child, layout_result, fragmentainer_block_offset,
appeal_before, builder))
return NGBreakStatus::kNeedsEarlierBreak;
return NGBreakStatus::kBrokeBefore;
}
void BreakBeforeChild(const NGConstraintSpace& space,
NGLayoutInputNode child,
const NGLayoutResult& layout_result,
......
......@@ -109,6 +109,30 @@ enum class NGBreakStatus {
kNeedsEarlierBreak
};
// Insert a fragmentainer break before the child if necessary. In that case, the
// previous in-flow position will be updated, we'll return |kBrokeBefore|. If we
// don't break inside, we'll consider the appeal of doing so anyway (and store
// it as the most appealing break point so far if that's the case), since we
// might have to go back and break here. Return |kContinue| if we're to continue
// laying out. If |kNeedsEarlierBreak| is returned, it means that we ran out of
// space, but shouldn't break before the child, but rather abort layout, and
// re-layout to a previously found good breakpoint. If
// |has_container_separation| is true, it means that we're at a valid
// breakpoint. We obviously prefer valid breakpoints, but sometimes we need to
// break at undesirable locations. Class A breakpoints occur between block
// siblings. Class B breakpoints between line boxes. Both these breakpoint
// classes imply that we're already past the first in-flow child in the
// container, but there's also another way of achieving container separation:
// class C breakpoints. Those occur if there's a positive gap between the
// block-start content edge of the container and the block-start margin edge of
// the first in-flow child. https://www.w3.org/TR/css-break-3/#possible-breaks
NGBreakStatus BreakBeforeChildIfNeeded(const NGConstraintSpace&,
NGLayoutInputNode child,
const NGLayoutResult&,
LayoutUnit fragmentainer_block_offset,
bool has_container_separation,
NGBoxFragmentBuilder*);
// Insert a break before the child, and propagate space shortage if needed.
void BreakBeforeChild(const NGConstraintSpace&,
NGLayoutInputNode child,
......
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