blob: fff17dbb5eda51ccdad4235883e4b458a94d9914 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_FRAGMENTATION_UTILS_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_FRAGMENTATION_UTILS_H_
#include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_node.h"
#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h"
#include "third_party/blink/renderer/core/style/computed_style_constants.h"
#include "third_party/blink/renderer/platform/geometry/layout_unit.h"
namespace blink {
class NGLayoutResult;
// Join two adjacent break values specified on break-before and/or break-
// after. avoid* values win over auto values, and forced break values win over
// avoid* values. |first_value| is specified on an element earlier in the flow
// than |second_value|. This method is used at class A break points [1], to join
// the values of the previous break-after and the next break-before, to figure
// out whether we may, must, or should not break at that point. It is also used
// when propagating break-before values from first children and break-after
// values on last children to their container.
//
// [1] https://drafts.csswg.org/css-break/#possible-breaks
EBreakBetween JoinFragmentainerBreakValues(EBreakBetween first_value,
EBreakBetween second_value);
// Return true if the specified break value has a forced break effect in the
// current fragmentation context.
bool IsForcedBreakValue(const NGConstraintSpace&, EBreakBetween);
// Return true if the specified break value means that we should avoid breaking,
// given the current fragmentation context.
template <typename Property>
bool IsAvoidBreakValue(const NGConstraintSpace&, Property);
// Return true if we're resuming layout after a previous break.
inline bool IsResumingLayout(const NGBlockBreakToken* token) {
return token && !token->IsBreakBefore();
}
// Calculate the final "break-between" value at a class A or C breakpoint. This
// is the combination of all break-before and break-after values that met at the
// breakpoint.
EBreakBetween CalculateBreakBetweenValue(NGLayoutInputNode child,
const NGLayoutResult&,
const NGBoxFragmentBuilder&);
// Calculate the appeal of breaking before this child.
NGBreakAppeal CalculateBreakAppealBefore(const NGConstraintSpace&,
NGLayoutInputNode child,
const NGLayoutResult&,
const NGBoxFragmentBuilder&,
bool has_container_separation);
// Calculate the appeal of breaking inside this child.
NGBreakAppeal CalculateBreakAppealInside(const NGConstraintSpace& space,
NGBlockNode child,
const NGLayoutResult&);
// To ensure content progression, we need fragmentainers to hold something
// larger than 0. The spec says that fragmentainers have to accept at least 1px
// of content. See https://www.w3.org/TR/css-break-3/#breaking-rules
inline LayoutUnit ClampedToValidFragmentainerCapacity(LayoutUnit length) {
return std::max(length, LayoutUnit(1));
}
// Return the fragmentainer block-size to use during layout. This is normally
// the same as the block-size we'll give to the fragment itself, but in order to
// ensure content progression, we need fragmentainers to hold something larger
// than 0 (even if the final fragentainer size may very well be 0). The spec
// says that fragmentainers have to accept at least 1px of content. See
// https://www.w3.org/TR/css-break-3/#breaking-rules
inline LayoutUnit FragmentainerCapacity(const NGConstraintSpace& space) {
if (!space.HasKnownFragmentainerBlockSize())
return kIndefiniteSize;
return ClampedToValidFragmentainerCapacity(space.FragmentainerBlockSize());
}
// Return the block space that was available in the current fragmentainer at the
// start of the current block formatting context. Note that if the start of the
// current block formatting context is in a previous fragmentainer, the size of
// the current fragmentainer is returned instead.
// In the case of initial column balancing, the size is unknown, in which case
// kIndefiniteSize is returned.
inline LayoutUnit FragmentainerSpaceAtBfcStart(const NGConstraintSpace& space) {
if (!space.HasKnownFragmentainerBlockSize())
return kIndefiniteSize;
return FragmentainerCapacity(space) - space.FragmentainerOffsetAtBfc();
}
// Adjust margins to take fragmentation into account. Leading/trailing block
// margins must be applied to at most one fragment each. Leading block margins
// come before the first fragment (if at all; see below), and trailing block
// margins come right after the fragment that has any trailing padding+border
// (note that this may not be the final fragment, if children overflow; see
// below). For all other fragments, leading/trailing block margins must be
// ignored.
inline void AdjustMarginsForFragmentation(const NGBlockBreakToken* break_token,
NGBoxStrut* box_strut) {
if (!break_token)
return;
// Leading block margins are truncated if they come right after an unforced
// break (except for floats; floats never truncate margins). And they should
// only occur in front of the first fragment.
if (!break_token->IsBreakBefore() ||
(!break_token->IsForcedBreak() && !break_token->InputNode().IsFloating()))
box_strut->block_start = LayoutUnit();
// If we're past the block end, we are in a parallel flow (caused by content
// overflow), and any trailing block margin has already been applied in the
// fragmentainer where the block actually ended.
if (break_token->IsAtBlockEnd())
box_strut->block_end = LayoutUnit();
}
// Set up a child's constraint space builder for block fragmentation. The child
// participates in the same fragmentation context as parent_space. If the child
// establishes a new formatting context, |fragmentainer_offset_delta| must be
// set to the offset from the parent block formatting context, or, if the parent
// formatting context starts in a previous fragmentainer; the offset from the
// current fragmentainer block-start.
void SetupSpaceBuilderForFragmentation(const NGConstraintSpace& parent_space,
const NGLayoutInputNode& child,
LayoutUnit fragmentainer_offset_delta,
NGConstraintSpaceBuilder*,
bool is_new_fc);
// Set up a node's fragment builder for block fragmentation. To be done at the
// beginning of layout.
void SetupFragmentBuilderForFragmentation(
const NGConstraintSpace&,
const NGBlockBreakToken* previous_break_token,
NGBoxFragmentBuilder*);
// Return true if the node is fully grown at its current size.
// |current_total_block_size| is the total block-size of the node, as if all
// fragments were stitched together.
bool IsNodeFullyGrown(NGBlockNode,
const NGConstraintSpace&,
LayoutUnit current_total_block_size,
const NGBoxStrut& border_padding,
LayoutUnit inline_size);
// Update and write fragmentation information to the fragment builder after
// layout. This will update the block-size stored in the builder. When
// calculating the block-size, a layout algorithm will include the accumulated
// block-size of all fragments generated for this node - as if they were all
// stitched together as one tall fragment. This is the most convenient thing to
// do, since any block-size specified in CSS applies to the entire box,
// regardless of fragmentation. This function will update the block-size to the
// actual fragment size, by examining possible breakpoints, if necessary.
//
// Return true if successful. If false is returned, it means that we ran out of
// space at a less-than-ideal location - in this case between the last child and
// the block-end padding / border. Furthermore, this also means that we know
// that we have a better earlier breakpoint, so the correct response to 'false'
// is to abort layout, then relayout and break earlier.
bool FinishFragmentation(NGBlockNode node,
const NGConstraintSpace&,
LayoutUnit trailing_border_padding,
LayoutUnit space_left,
NGBoxFragmentBuilder*);
// Outcome of considering (and possibly attempting) breaking before a child.
enum class NGBreakStatus {
// Continue layout. No break was inserted before the child (but there may be
// a break inside).
kContinue,
// A break was inserted before the child. Discard the child fragment and
// finish layout of the container. If there was a break inside the child, it
// will be discarded along with the child fragment.
kBrokeBefore,
// The child couldn't fit here, but no break was inserted before the child,
// as it was an unappealing place to break, and we have a better earlier
// breakpoint. We now need to abort the current layout, and go back and
// re-layout to said earlier breakpoint.
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,
const NGLayoutResult&,
LayoutUnit fragmentainer_block_offset,
base::Optional<NGBreakAppeal> appeal,
bool is_forced_break,
NGBoxFragmentBuilder*);
// Propagate the block-size of unbreakable content. This is used to inflate the
// initial minimal column block-size when balancing columns, before we calculate
// a tentative (or final) column block-size. Unbreakable content will actually
// fragment if the columns aren't large enough, and we want to prevent that, if
// possible.
inline void PropagateUnbreakableBlockSize(LayoutUnit block_size,
LayoutUnit fragmentainer_block_offset,
NGBoxFragmentBuilder* builder) {
// Whatever is before the block-start of the fragmentainer isn't considered to
// intersect with the fragmentainer, so subtract it (by adding the negative
// offset).
if (fragmentainer_block_offset < LayoutUnit())
block_size += fragmentainer_block_offset;
builder->PropagateTallestUnbreakableBlockSize(block_size);
}
// Propagate space shortage to the builder and beyond, if appropriate. This is
// something we do during column balancing, when we already have a tentative
// column block-size, as a means to calculate by how much we need to stretch the
// columns to make everything fit.
void PropagateSpaceShortage(const NGConstraintSpace&,
const NGLayoutResult&,
LayoutUnit fragmentainer_block_offset,
NGBoxFragmentBuilder*);
// Move past the breakpoint before the child, if possible, and return true. Also
// update the appeal of breaking before or inside the child (if we're not going
// to break before it). If false is returned, it means that we need to break
// before the child (or even earlier).
bool MovePastBreakpoint(const NGConstraintSpace& space,
NGLayoutInputNode child,
const NGLayoutResult& layout_result,
LayoutUnit fragmentainer_block_offset,
NGBreakAppeal appeal_before,
NGBoxFragmentBuilder* builder);
// If the appeal of breaking before or inside the child is the same or higher
// than any previous breakpoint we've found, set a new breakpoint in the
// builder, and update appeal accordingly.
void UpdateEarlyBreakAtBlockChild(const NGConstraintSpace&,
NGBlockNode child,
const NGLayoutResult&,
NGBreakAppeal appeal_before,
NGBoxFragmentBuilder*);
// Attempt to insert a soft break before the child, and return true if we did.
// If false is returned, it means that the desired breakpoint is earlier in the
// container, and that we need to abort and re-layout to that breakpoint.
bool AttemptSoftBreak(const NGConstraintSpace&,
NGLayoutInputNode child,
const NGLayoutResult&,
LayoutUnit fragmentainer_block_offset,
NGBreakAppeal appeal_before,
NGBoxFragmentBuilder*);
// Calculate the constraint space for columns of a multi-column layout.
NGConstraintSpace CreateConstraintSpaceForColumns(
const NGConstraintSpace& parent_space,
LogicalSize column_size,
LogicalSize percentage_resolution_size,
bool allow_discard_start_margin,
bool balance_columns);
// Calculate the container builder and constraint space for a multicol.
NGBoxFragmentBuilder CreateContainerBuilderForMulticol(
const NGBlockNode& multicol,
const NGConstraintSpace& space,
const NGFragmentGeometry& fragment_geometry);
NGConstraintSpace CreateConstraintSpaceForMulticol(const NGBlockNode& multicol);
// Return the adjusted child margin to be applied at the end of a fragment.
// Margins should collapse with the fragmentainer boundary. |bfc_block_offset|
// is the BFC offset where the margin should be applied (i.e. after the
// block-end border edge of the last child fragment).
inline LayoutUnit AdjustedMarginAfterFinalChildFragment(
const NGConstraintSpace& space,
LayoutUnit bfc_block_offset,
LayoutUnit block_end_margin) {
LayoutUnit space_left =
FragmentainerSpaceAtBfcStart(space) - bfc_block_offset;
return std::min(block_end_margin, space_left.ClampNegativeToZero());
}
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_FRAGMENTATION_UTILS_H_