// Copyright 2017 The Chromium Authors // 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_FRAGMENTATION_UTILS_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_FRAGMENTATION_UTILS_H_ #include <optional> #include "third_party/blink/renderer/core/layout/block_break_token.h" #include "third_party/blink/renderer/core/layout/block_node.h" #include "third_party/blink/renderer/core/layout/box_fragment_builder.h" #include "third_party/blink/renderer/core/layout/column_spanner_path.h" #include "third_party/blink/renderer/core/layout/constraint_space.h" #include "third_party/blink/renderer/core/layout/geometry/box_strut.h" #include "third_party/blink/renderer/core/layout/inline/inline_break_token.h" #include "third_party/blink/renderer/core/layout/inline/inline_item.h" #include "third_party/blink/renderer/core/layout/physical_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 EarlyBreak; class LayoutResult; // Each column in a flex container is fragmented independently, so we need to // track early break and break-after info for each column separately. struct FlexColumnBreakInfo { … }; // 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 ConstraintSpace&, 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 ConstraintSpace&, Property); // Return true if this is a break inside a node (i.e. it's not a break *before* // something, and also not for repeated content). inline bool IsBreakInside(const BlockBreakToken* token) { … } // Return true if the node may break into multiple fragments (or has already // broken). In some situations we'll disable block fragmentation while in the // middle of layout of a node (to prevent superfluous empty fragments, if // overflow is clipped). In some cases it's not enough to just check if we're // currently performing block fragmentation; we also need to know if it has // already been fragmented (to resume layout correctly, but not break again). inline bool InvolvedInBlockFragmentation(const BoxFragmentBuilder& builder) { … } // Return the fragment index (into the layout results vector in LayoutBox), // based on incoming break token. inline wtf_size_t FragmentIndex(const BlockBreakToken* incoming_break_token) { … } // 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(LayoutInputNode child, const LayoutResult&, const BoxFragmentBuilder&); // Return true if the container is being resumed after a fragmentainer break, // and the child is at the first fragment of a node, and we are allowed to break // before it. Normally, this isn't allowed, as that would take us nowhere, // progress-wise, but for multicol in nested fragmentation, we'll allow it in // some cases. If we set the appeal of breaking before the first child high // enough, we'll automatically discard any subsequent less perfect // breakpoints. This will make us push everything that would break with an // appeal lower than the minimum appeal (stored in the constraint space) ahead // of us, until we reach the next column row (in the next outer fragmentainer). // That row may be taller, which might help us avoid breaking violations. bool IsBreakableAtStartOfResumedContainer( const ConstraintSpace& space, const LayoutResult& child_layout_result, const BoxFragmentBuilder& builder); bool IsBreakableAtStartOfResumedContainer(const ConstraintSpace& space, const BoxFragmentBuilder& builder, bool is_first_for_node); // Calculate the appeal of breaking before this child. BreakAppeal CalculateBreakAppealBefore(const ConstraintSpace&, LayoutInputNode child, const LayoutResult&, const BoxFragmentBuilder&, bool has_container_separation); BreakAppeal CalculateBreakAppealBefore( const ConstraintSpace&, LayoutResult::EStatus layout_result_status, EBreakBetween break_between, bool has_container_separation, bool breakable_at_start_of_container); // Calculate the appeal of breaking inside this child. The appeal is based on // the one stored in the layout result, unless hypothetical_appeal is specified. // hypothetical_appeal is used to assess the appeal at breakpoints where we // didn't break, but still need to consider (see EarlyBreak). BreakAppeal CalculateBreakAppealInside( const ConstraintSpace& space, const LayoutResult&, std::optional<BreakAppeal> hypothetical_appeal = std::nullopt); // 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) { … } // This function is most commonly used to figure out space available to children // of a builder, but if it's used to figure out the minimum valid fragmentainer // size for the fragment itself, `is_for_children` may be cleared, so that any // cloned box decorations are included. Such box decorations will otherwise be // subtracted, since children should steer clear of them. inline LayoutUnit ClampedToValidFragmentainerCapacity( const BoxFragmentBuilder& builder, LayoutUnit length, bool is_for_children) { … } // Return the logical size of the specified fragmentainer, with // clamping block_size. LogicalSize FragmentainerLogicalCapacity( const PhysicalBoxFragment& fragmentainer); // 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 // // This function is most commonly used to figure out space available to children // of a builder, but if it's used to figure out the space available the fragment // itself, `is_for_children` may be cleared, so that any cloned box decorations // are included. Such box decorations will otherwise be subtracted, since // children should steer clear of them. inline LayoutUnit FragmentainerCapacity(const BoxFragmentBuilder& builder, bool is_for_children) { … } // Get the start block-offset relatively to the fragmentainer start. // // This function is most commonly called for children of a builder, but if it's // used to figure out the offset for the fragment itself, `is_for_children` may // be cleared, so that any cloned box decorations are included. Such box // decorations will otherwise be subtracted, since children should steer clear // of them. inline LayoutUnit FragmentainerOffset(const BoxFragmentBuilder& builder, bool is_for_children = true) { … } // Return the block space that was available in the current fragmentainer at the // start of the current block. Note that if the start of the current block is in // a previous fragmentainer, the size of the current fragmentainer is returned // instead. If available space is negative, zero is returned. In the case of // initial column balancing, the size is unknown, in which case kIndefiniteSize // is returned. // // This function is most commonly used to figure out space available to children // of a builder, but if it's used to figure out the space available the fragment // itself, `is_for_children` may be cleared, so that any cloned box decorations // are included. Such box decorations will otherwise be subtracted, since // children should steer clear of them. inline LayoutUnit FragmentainerSpaceLeft(const BoxFragmentBuilder& builder, bool is_for_children) { … } // Return the border edge block-offset from the block-start of the fragmentainer // relative to the block-start of the current block formatting context in the // current fragmentainer. Note that if the current block formatting context // starts in a previous fragmentainer, we'll return the block-offset relative to // the current fragmentainer. inline LayoutUnit FragmentainerOffsetAtBfc(const ConstraintSpace& space) { … } inline LayoutUnit FragmentainerOffsetAtBfc(const BoxFragmentBuilder& builder) { … } // 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 BlockBreakToken* break_token, BoxStrut* box_strut) { … } // Get the offset from one fragmentainer to the next. LogicalOffset GetFragmentainerProgression(const BoxFragmentBuilder&, FragmentationType); // Set up a child's constraint space builder for block fragmentation. The child // participates in the same fragmentation context as parent_space. // |requires_content_before_breaking| is set when inside node that we know will // fit (and stay) in the current fragmentainer. See // MustStayInCurrentFragmentainer() in BoxFragmentBuilder. void SetupSpaceBuilderForFragmentation(const ConstraintSpace& parent_space, const LayoutInputNode& child, LayoutUnit fragmentainer_offset, LayoutUnit fragmentainer_block_size, bool requires_content_before_breaking, ConstraintSpaceBuilder*); // 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 BoxFragmentBuilder& parent_fragment_builder, const LayoutInputNode& child, LayoutUnit fragmentainer_offset_delta, ConstraintSpaceBuilder*); // Set up a node's fragment builder for block fragmentation. To be done at the // beginning of layout. void SetupFragmentBuilderForFragmentation( const ConstraintSpace&, const LayoutInputNode&, const BlockBreakToken* previous_break_token, BoxFragmentBuilder*); // Return whether any block-start border+padding should be included in the // fragment being generated. Only one of the fragments should include this, // unless box decorations are to be cloned. bool ShouldIncludeBlockStartBorderPadding(const BoxFragmentBuilder&); // Return whether any block-end border+padding should be included in the // fragment being generated. Only one of the fragments should include this, // unless box decorations are to be cloned. bool ShouldIncludeBlockEndBorderPadding(const BoxFragmentBuilder&); // Return the size of the block-start box decorations, if they are cloned. In // the cloning box decoration model, block-start box decoration are considered // cloned in all fragments but the first. inline LayoutUnit ClonedBlockStartDecoration( const BoxFragmentBuilder& builder) { … } // Outcome of considering (and possibly attempting) breaking before or inside a // child. enum class BreakStatus { … }; // Update and write fragmentation information to the fragment builder after // layout. This will update the block-size stored in the builder. It may also // update the stored intrinsic block-size. // // 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 kContinue if we're allowed to generate a fragment. Otherwise, it means // that we need to abort and relayout, either because we ran out of space at a // less-than-ideal location (kNeedsEarlierBreak) - in this case between the last // child and the block-end padding / border, or, because we need to disable // fragmentation (kDisableFragmentation). kBrokeBefore is never returned here // (if we need a break before the node, that's something that will be determined // by the parent algorithm). BreakStatus FinishFragmentation(BoxFragmentBuilder*); // Special rules apply for finishing fragmentation when building fragmentainers. BreakStatus FinishFragmentationForFragmentainer(BoxFragmentBuilder*); // Return true if there's a valid class A/B breakpoint between the child // fragment that was just added to the builder, and the next sibling, if one is // added. bool HasBreakOpportunityBeforeNextChild( const PhysicalFragment& child_fragment, const BreakToken* incoming_child_break_token); // 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. |kDisableFragmentation| will // never be returned from this function (we need to finish layout of the // container before we can tell whether we reached the end). 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 // If |flex_column_break_info| is supplied, we are running layout for a column // flex container, in which case, we may be tracking certain break behavior at // the column level. BreakStatus BreakBeforeChildIfNeeded( const ConstraintSpace&, LayoutInputNode child, const LayoutResult&, LayoutUnit fragmentainer_block_offset, LayoutUnit fragmentainer_block_size, bool has_container_separation, BoxFragmentBuilder*, bool is_row_item = false, FlexColumnBreakInfo* flex_column_break_info = nullptr); // Insert a break before the child, and propagate space shortage if needed. // |block_size_override| should only be supplied when you wish to propagate a // different block-size than that of the provided layout result. void BreakBeforeChild( const ConstraintSpace&, LayoutInputNode child, const LayoutResult*, LayoutUnit fragmentainer_block_offset, LayoutUnit fragmentainer_block_size, std::optional<BreakAppeal> appeal, bool is_forced_break, BoxFragmentBuilder*, std::optional<LayoutUnit> block_size_override = std::nullopt); // 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, BoxFragmentBuilder* builder) { … } // 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. |block_size_override| should only be supplied // when you wish to propagate a different block-size than that of the provided // layout result. void PropagateSpaceShortage( const ConstraintSpace&, const LayoutResult*, LayoutUnit fragmentainer_block_offset, LayoutUnit fragmentainer_block_size, FragmentBuilder*, std::optional<LayoutUnit> block_size_override = std::nullopt); // Calculate how much we would need to stretch the column block-size to fit the // current result (if applicable). |block_size_override| should only be supplied // when you wish to propagate a different block-size than that of the provided // layout result. LayoutUnit CalculateSpaceShortage( const ConstraintSpace&, const LayoutResult*, LayoutUnit fragmentainer_block_offset, LayoutUnit fragmentainer_block_size, std::optional<LayoutUnit> block_size_override = std::nullopt); // Update |minimal_space_shortage| based on the current |space_shortage|. void UpdateMinimalSpaceShortage(std::optional<LayoutUnit> space_shortage, LayoutUnit* minimal_space_shortage); // 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). See BreakBeforeChildIfNeeded() for // details on |flex_column_break_info|. bool MovePastBreakpoint(const ConstraintSpace& space, LayoutInputNode child, const LayoutResult& layout_result, LayoutUnit fragmentainer_block_offset, LayoutUnit fragmentainer_block_size, BreakAppeal appeal_before, BoxFragmentBuilder* builder, bool is_row_item = false, FlexColumnBreakInfo* flex_column_break_info = nullptr); // Same as above, but without the parts that require an LayoutInputNode. bool MovePastBreakpoint(const ConstraintSpace& space, const LayoutResult& layout_result, LayoutUnit fragmentainer_block_offset, LayoutUnit fragmentainer_block_size, BreakAppeal appeal_before, BoxFragmentBuilder* builder, bool is_row_item = false, FlexColumnBreakInfo* flex_column_break_info = nullptr); // 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. See BreakBeforeChildIfNeeded() for // details on |flex_column_break_info|. void UpdateEarlyBreakAtBlockChild( const ConstraintSpace&, BlockNode child, const LayoutResult&, BreakAppeal appeal_before, BoxFragmentBuilder*, FlexColumnBreakInfo* flex_column_break_info = nullptr); // 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. // |block_size_override| should only be supplied when you wish to propagate a // different block-size than that of the provided layout result. See // BreakBeforeChildIfNeeded() for details on |flex_column_break_info|. bool AttemptSoftBreak( const ConstraintSpace&, LayoutInputNode child, const LayoutResult*, LayoutUnit fragmentainer_block_offset, LayoutUnit fragmentainer_block_size, BreakAppeal appeal_before, BoxFragmentBuilder*, std::optional<LayoutUnit> block_size_override = std::nullopt, FlexColumnBreakInfo* flex_column_break_info = nullptr); // If we have an previously found break point, and we're entering an ancestor of // the node we're going to break before, return the early break inside. This can // then be passed to child layout, so that child layout eventually can tell // where to insert the break. const EarlyBreak* EnterEarlyBreakInChild(const BlockNode& child, const EarlyBreak&); // Return true if this is the child that we had previously determined to break // before. bool IsEarlyBreakTarget(const EarlyBreak&, const BoxFragmentBuilder&, const LayoutInputNode& child); // Find out if |child| is the next step on the column spanner path (if any), and // return the remaining path if that's the case, nullptr otherwise. inline const ColumnSpannerPath* FollowColumnSpannerPath( const ColumnSpannerPath* path, const BlockNode& child) { … } // Set up a constraint space for columns in multi-column layout, or for pages // when printing; as specified by fragmentation_type. ConstraintSpace CreateConstraintSpaceForFragmentainer( const ConstraintSpace& parent_space, FragmentationType fragmentation_type, LogicalSize fragmentainer_size, LogicalSize percentage_resolution_size, bool balance_columns, BreakAppeal min_break_appeal); // Calculate the container builder and constraint space for a multicol. BoxFragmentBuilder CreateContainerBuilderForMulticol( const BlockNode& multicol, const ConstraintSpace& space, const FragmentGeometry& fragment_geometry); ConstraintSpace CreateConstraintSpaceForMulticol(const BlockNode& multicol); // Return the adjusted child margin to be applied at the end of a fragment. // Margins should collapse with the fragmentainer boundary. |block_offset| is // the block-offset where the margin should be applied (i.e. after the block-end // border edge of the last child fragment). inline LayoutUnit AdjustedMarginAfterFinalChildFragment( const BoxFragmentBuilder& builder, LayoutUnit block_offset, LayoutUnit block_end_margin) { … } // Return the break token that led to the creation of the fragment specified, or // nullptr if this is the first fragment. Note that this operation is O(n) // (number of fragments generated from the node), and should be avoided when // possible. This function should no longer be necessary once everything has // been properly converted to LayoutNG. const BlockBreakToken* FindPreviousBreakToken(const PhysicalBoxFragment&); // Return the LayoutBox::PhysicalFragments() index for this fragment. wtf_size_t BoxFragmentIndex(const PhysicalBoxFragment&); // Return the index of the fragmentainer preceding the first fragmentainer // inside this fragment. Used by nested block fragmentation. wtf_size_t PreviousInnerFragmentainerIndex(const PhysicalBoxFragment&); // Return the fragment's offset relatively to the top/left corner of an // imaginary box where all fragments generated by the node have been stitched // together. If |out_stitched_fragments_size| is specified, it will be set to // the size of this imaginary box. PhysicalOffset OffsetInStitchedFragments( const PhysicalBoxFragment&, PhysicalSize* out_stitched_fragments_size = nullptr); // Return the block-size that this fragment will take up inside a fragmentation // context. This will include overflow from descendants (if it is visible and // supposed to affect block fragmentation), and also out-of-flow positioned // descendants (in the initial balancing pass), but not relative offsets. LayoutUnit BlockSizeForFragmentation( const LayoutResult&, WritingDirectionMode container_writing_direction); // Return true if we support painting of multiple fragments for the given // content. Will return true for anything that is fragmentable / non-monolithic. // Will also return true for certain types of monolithic content, because, even // if it's unbreakable, it may generate multiple fragments, if it's part of // repeated content (such as table headers and footers). This is the case for // e.g. images, which may for instance be repeated in table headers / // footers. Return false for monolithic content that we don't want to repeat // (e.g. iframes). bool CanPaintMultipleFragments(const PhysicalBoxFragment&); bool CanPaintMultipleFragments(const LayoutObject&); } // namespace blink WTF_ALLOW_CLEAR_UNUSED_SLOTS_WITH_MEM_FUNCTIONS(…) #endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_FRAGMENTATION_UTILS_H_