1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.h"
6 
7 #include <algorithm>
8 #include "third_party/blink/renderer/core/layout/geometry/logical_size.h"
9 #include "third_party/blink/renderer/core/layout/ng/geometry/ng_fragment_geometry.h"
10 #include "third_party/blink/renderer/core/layout/ng/geometry/ng_margin_strut.h"
11 #include "third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h"
12 #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h"
13 #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
14 #include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h"
15 #include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
16 #include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h"
17 #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
18 #include "third_party/blink/renderer/core/style/computed_style.h"
19 
20 namespace blink {
21 
22 namespace {
23 
CalculateColumnContentBlockSize(const NGPhysicalContainerFragment & fragment,bool multicol_is_horizontal_writing_mode)24 LayoutUnit CalculateColumnContentBlockSize(
25     const NGPhysicalContainerFragment& fragment,
26     bool multicol_is_horizontal_writing_mode) {
27   // TODO(mstensho): Once LayoutNG is capable of calculating overflow on its
28   // own, we should probably just move over to relying on that machinery,
29   // instead of doing all this on our own.
30   LayoutUnit total_size;
31   for (const auto& child : fragment.Children()) {
32     LayoutUnit size;
33     LayoutUnit offset;
34     if (multicol_is_horizontal_writing_mode) {
35       offset = child.Offset().top;
36       size = child->Size().height;
37     } else {
38       offset = child.Offset().left;
39       size = child->Size().width;
40     }
41     if (child->IsContainer()) {
42       LayoutUnit children_size = CalculateColumnContentBlockSize(
43           To<NGPhysicalContainerFragment>(*child),
44           multicol_is_horizontal_writing_mode);
45       if (size < children_size)
46         size = children_size;
47     }
48     LayoutUnit block_end = offset + size;
49     if (total_size < block_end)
50       total_size = block_end;
51   }
52   return total_size;
53 }
54 
IsColumnSpanner(NGBlockNode multicol_container,const NGBlockBreakToken & token)55 inline bool IsColumnSpanner(NGBlockNode multicol_container,
56                             const NGBlockBreakToken& token) {
57   // A column spanner may also establish a multicol container on its own, so
58   // before returning true here, make sure that the spanner isn't the multicol
59   // container itself.
60   NGLayoutInputNode broken_node = token.InputNode();
61   return broken_node.IsColumnSpanAll() && broken_node != multicol_container;
62 }
63 
64 // Add the break token for the column content that comes after a fragmented
65 // spanner, if any; otherwise, we're past all children.
PushNextColumnBreakToken(scoped_refptr<const NGBlockBreakToken> next_column_token,NGBoxFragmentBuilder * builder)66 void PushNextColumnBreakToken(
67     scoped_refptr<const NGBlockBreakToken> next_column_token,
68     NGBoxFragmentBuilder* builder) {
69   if (next_column_token)
70     builder->AddBreakToken(std::move(next_column_token));
71   else
72     builder->SetHasSeenAllChildren();
73 }
74 
75 // Add the spanner's break token, AND another break token for the column content
76 // that comes after. In the next fragment we need to resume layout of the
77 // spanner, and then proceed to the column content - if there's room for both.
78 // Note that it's possible for the spanner to break again in the next fragment.
PushSpannerBreakTokens(scoped_refptr<const NGBlockBreakToken> spanner_break_token,scoped_refptr<const NGBlockBreakToken> next_column_token,NGBoxFragmentBuilder * builder)79 void PushSpannerBreakTokens(
80     scoped_refptr<const NGBlockBreakToken> spanner_break_token,
81     scoped_refptr<const NGBlockBreakToken> next_column_token,
82     NGBoxFragmentBuilder* builder) {
83   builder->AddBreakToken(std::move(spanner_break_token));
84   PushNextColumnBreakToken(std::move(next_column_token), builder);
85 }
86 
87 }  // namespace
88 
NGColumnLayoutAlgorithm(const NGLayoutAlgorithmParams & params)89 NGColumnLayoutAlgorithm::NGColumnLayoutAlgorithm(
90     const NGLayoutAlgorithmParams& params)
91     : NGLayoutAlgorithm(params),
92       early_break_(params.early_break),
93       border_padding_(params.fragment_geometry.border +
94                       params.fragment_geometry.padding),
95       border_scrollbar_padding_(border_padding_ +
96                                 params.fragment_geometry.scrollbar) {
97   AdjustForFragmentation(BreakToken(), &border_scrollbar_padding_);
98   container_builder_.SetIsNewFormattingContext(
99       params.space.IsNewFormattingContext());
100   container_builder_.SetInitialFragmentGeometry(params.fragment_geometry);
101 }
102 
Layout()103 scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::Layout() {
104   LogicalSize border_box_size = container_builder_.InitialBorderBoxSize();
105   content_box_size_ =
106       ShrinkAvailableSize(border_box_size, border_scrollbar_padding_);
107 
108   DCHECK_GE(content_box_size_.inline_size, LayoutUnit());
109   column_inline_size_ =
110       ResolveUsedColumnInlineSize(content_box_size_.inline_size, Style());
111 
112   column_inline_progression_ =
113       column_inline_size_ +
114       ResolveUsedColumnGap(content_box_size_.inline_size, Style());
115   used_column_count_ =
116       ResolveUsedColumnCount(content_box_size_.inline_size, Style());
117 
118   // If we know the block-size of the fragmentainers in an outer fragmentation
119   // context (if any), our columns may be constrained by that, meaning that we
120   // may have to fragment earlier than what we would have otherwise, and, if
121   // that's the case, that we may also not create overflowing columns (in the
122   // inline axis), but rather finish the row and resume in the next row in the
123   // next outer fragmentainer. Note that it is possible to be nested inside a
124   // fragmentation context that doesn't know the block-size of its
125   // fragmentainers. This would be in the first layout pass of an outer multicol
126   // container, before any tentative column block-size has been calculated.
127   is_constrained_by_outer_fragmentation_context_ =
128       ConstraintSpace().HasKnownFragmentainerBlockSize();
129 
130   if (ConstraintSpace().HasBlockFragmentation()) {
131     container_builder_.SetHasBlockFragmentation();
132     if (ConstraintSpace().IsInitialColumnBalancingPass())
133       container_builder_.SetIsInitialColumnBalancingPass();
134   }
135 
136   container_builder_.SetIsBlockFragmentationContextRoot();
137 
138   intrinsic_block_size_ = border_scrollbar_padding_.block_start;
139 
140   NGBreakStatus break_status = LayoutChildren();
141   if (break_status == NGBreakStatus::kNeedsEarlierBreak) {
142     // We need to discard this layout and do it again. We found an earlier break
143     // point that's more appealing than the one we ran out of space at.
144     return RelayoutAndBreakEarlier();
145   } else if (break_status == NGBreakStatus::kBrokeBefore) {
146     // If we want to break before, make sure that we're actually at the start.
147     DCHECK(!BreakToken());
148 
149     return container_builder_.Abort(NGLayoutResult::kOutOfFragmentainerSpace);
150   }
151 
152   // Figure out how much space we've already been able to process in previous
153   // fragments, if this multicol container participates in an outer
154   // fragmentation context.
155   LayoutUnit previously_consumed_block_size;
156   if (const auto* token = BreakToken())
157     previously_consumed_block_size = token->ConsumedBlockSize();
158 
159   // TODO(mstensho): Propagate baselines.
160 
161   LayoutUnit block_size;
162   if (border_box_size.block_size == kIndefiniteSize) {
163     // Get the block size from the contents if it's auto.
164     block_size = intrinsic_block_size_ + border_scrollbar_padding_.block_end;
165   } else {
166     // TODO(mstensho): end border and padding may overflow the parent
167     // fragmentainer, and we should avoid that.
168     block_size = border_box_size.block_size - previously_consumed_block_size;
169   }
170 
171   if (is_constrained_by_outer_fragmentation_context_) {
172     // In addition to establishing one, we're nested inside another
173     // fragmentation context.
174     FinishFragmentation(
175         ConstraintSpace(), BreakToken(), block_size, intrinsic_block_size_,
176         FragmentainerSpaceAtBfcStart(ConstraintSpace()), &container_builder_);
177   } else {
178     container_builder_.SetBlockSize(block_size);
179     container_builder_.SetIntrinsicBlockSize(intrinsic_block_size_);
180   }
181 
182   NGOutOfFlowLayoutPart(
183       Node(), ConstraintSpace(),
184       container_builder_.Borders() + container_builder_.Scrollbar(),
185       &container_builder_)
186       .Run();
187 
188   return container_builder_.ToBoxFragment();
189 }
190 
ComputeMinMaxSizes(const MinMaxSizesInput & input) const191 base::Optional<MinMaxSizes> NGColumnLayoutAlgorithm::ComputeMinMaxSizes(
192     const MinMaxSizesInput& input) const {
193   // First calculate the min/max sizes of columns.
194   NGConstraintSpace space = CreateConstraintSpaceForMinMax();
195   NGFragmentGeometry fragment_geometry =
196       CalculateInitialMinMaxFragmentGeometry(space, Node());
197   NGBlockLayoutAlgorithm algorithm({Node(), fragment_geometry, space});
198   base::Optional<MinMaxSizes> min_max_sizes =
199       algorithm.ComputeMinMaxSizes(input);
200   DCHECK(min_max_sizes.has_value());
201   MinMaxSizes sizes = *min_max_sizes;
202 
203   // If column-width is non-auto, pick the larger of that and intrinsic column
204   // width.
205   if (!Style().HasAutoColumnWidth()) {
206     sizes.min_size =
207         std::max(sizes.min_size, LayoutUnit(Style().ColumnWidth()));
208     sizes.max_size = std::max(sizes.max_size, sizes.min_size);
209   }
210 
211   // Now convert those column min/max values to multicol container min/max
212   // values. We typically have multiple columns and also gaps between them.
213   int column_count = Style().ColumnCount();
214   DCHECK_GE(column_count, 1);
215   sizes.min_size *= column_count;
216   sizes.max_size *= column_count;
217   LayoutUnit column_gap = ResolveUsedColumnGap(LayoutUnit(), Style());
218   sizes += column_gap * (column_count - 1);
219 
220   // TODO(mstensho): Need to include spanners.
221 
222   sizes += border_scrollbar_padding_.InlineSum();
223   return sizes;
224 }
225 
LayoutChildren()226 NGBreakStatus NGColumnLayoutAlgorithm::LayoutChildren() {
227   NGMarginStrut margin_strut;
228 
229   // First extract incoming child break tokens.
230   scoped_refptr<const NGBlockBreakToken> spanner_break_token;
231   scoped_refptr<const NGBlockBreakToken> next_column_token;
232   if (const auto* token = BreakToken()) {
233     // We're resuming layout of this multicol container after an outer
234     // fragmentation break. Resume at the break token of the last column that we
235     // were able to lay out, or before or inside the spanner that caused an
236     // outer fragmentainer break. Note that in some cases, there may be no child
237     // break tokens. That happens if we weren't able to lay out anything at all
238     // in the previous outer fragmentainer, e.g. due to a forced break before
239     // this multicol container, or e.g. if there was leading unbreakable content
240     // that couldn't fit in the space we were offered back then. In other words,
241     // in that case, we're about to create the first fragment for this multicol
242     // container.
243     const auto child_tokens = token->ChildBreakTokens();
244     if (wtf_size_t break_token_count = child_tokens.size()) {
245       wtf_size_t break_token_idx = 0;
246       scoped_refptr<const NGBlockBreakToken> child_token =
247           To<NGBlockBreakToken>(child_tokens[break_token_idx++]);
248       if (child_token && IsColumnSpanner(Node(), *child_token)) {
249         // We're resuming at a column spanner. Get the next break token after
250         // the spanner, if any. That would be the column content to resume at,
251         // once we're done with the spanner.
252         spanner_break_token = child_token;
253         if (break_token_idx < break_token_count) {
254           next_column_token =
255               To<NGBlockBreakToken>(child_tokens[break_token_idx++]);
256         }
257       } else {
258         next_column_token = child_token;
259       }
260       // There shouldn't be any additional break tokens.
261       DCHECK_EQ(break_token_idx, break_token_count);
262     }
263 
264     if (token->HasSeenAllChildren())
265       container_builder_.SetHasSeenAllChildren();
266   }
267 
268   if (spanner_break_token) {
269     // The multicol container previously broke at a spanner (this may happen if
270     // we're nested inside another fragmentation context), so that's where we'll
271     // resume now.
272     NGBreakStatus break_status = LayoutSpanner(
273         To<NGBlockNode>(spanner_break_token->InputNode()),
274         spanner_break_token.get(), &margin_strut, &spanner_break_token);
275 
276     if (spanner_break_token) {
277       DCHECK_EQ(break_status, NGBreakStatus::kContinue);
278       if (spanner_break_token) {
279         // We broke at the spanner again!
280         PushSpannerBreakTokens(std::move(spanner_break_token),
281                                std::move(next_column_token),
282                                &container_builder_);
283         return NGBreakStatus::kContinue;
284       }
285     } else {
286       // Breaking before the first element in the fragmentainer isn't allowed,
287       // as that would give no content progress, and we'd be stuck forever.
288       DCHECK_EQ(break_status, NGBreakStatus::kContinue);
289     }
290   }
291 
292   if (BreakToken() && BreakToken()->HasSeenAllChildren() && !next_column_token)
293     return NGBreakStatus::kContinue;
294 
295   // Entering the child main loop. Here we'll alternate between laying out
296   // column content and column spanners, until we're either done, or until
297   // something breaks. Spanners are discovered as part of laying out a row, so
298   // we'll always start with attempting to lay out a row, even if the first
299   // child is a spanner.
300   do {
301     scoped_refptr<const NGLayoutResult> result =
302         LayoutRow(next_column_token.get(), &margin_strut);
303 
304     if (!result) {
305       // Not enough outer fragmentainer space to produce any columns at all.
306       container_builder_.SetDidBreak();
307       if (intrinsic_block_size_) {
308         // We have preceding initial border/padding, or a column spanner
309         // (possibly preceded by other spanners or even column content). So we
310         // need to break inside the multicol container. Stop walking the
311         // children, but "continue" layout, so that we produce a fragment. Note
312         // that we normally don't want to break right after initial
313         // border/padding, but will do so as a last resort. It's up to our
314         // containing block to decide what's best.
315         FinishAfterBreakBeforeRow(std::move(next_column_token));
316         return NGBreakStatus::kContinue;
317       }
318       // Otherwise we have nothing here, and need to break before the multicol
319       // container. No fragment will be produced.
320       return NGBreakStatus::kBrokeBefore;
321     }
322 
323     next_column_token =
324         To<NGBlockBreakToken>(result->PhysicalFragment().BreakToken());
325 
326     // If we didn't find a spanner, it either means that we're through
327     // everything, or that column layout needs to continue from the next outer
328     // fragmentainer.
329     NGBlockNode spanner_node = result->ColumnSpanner();
330     if (!spanner_node)
331       break;
332 
333     if (early_break_) {
334       // If this is the child we had previously determined to break before, do
335       // so now and finish layout.
336       DCHECK_EQ(early_break_->Type(), NGEarlyBreak::kBlock);
337       if (early_break_->IsBreakBefore() &&
338           early_break_->BlockNode() == spanner_node) {
339         container_builder_.AddBreakBeforeChild(
340             spanner_node, kBreakAppealPerfect, /* is_forced_break */ false);
341         FinishAfterBreakBeforeSpanner(std::move(next_column_token));
342         return NGBreakStatus::kContinue;
343       }
344     }
345 
346     // We found a spanner. Lay it out, and then resume column layout.
347     NGBreakStatus break_status = LayoutSpanner(
348         spanner_node, nullptr, &margin_strut, &spanner_break_token);
349     if (break_status == NGBreakStatus::kNeedsEarlierBreak) {
350       return break_status;
351     } else if (break_status == NGBreakStatus::kBrokeBefore) {
352       DCHECK(ConstraintSpace().HasBlockFragmentation());
353       FinishAfterBreakBeforeSpanner(std::move(next_column_token));
354       return NGBreakStatus::kContinue;
355     } else if (spanner_break_token) {
356       DCHECK_EQ(break_status, NGBreakStatus::kContinue);
357       // We broke inside the spanner. This may happen if we're nested inside
358       // another fragmentation context.
359       PushSpannerBreakTokens(std::move(spanner_break_token),
360                              std::move(next_column_token), &container_builder_);
361       return NGBreakStatus::kContinue;
362     }
363   } while (next_column_token);
364 
365   // If there's an early break set, we should have found it and returned.
366   DCHECK(!early_break_);
367 
368   if (next_column_token) {
369     // We broke inside column content. Add a break token for where to resume
370     // column layout at in the next fragment.
371     container_builder_.AddBreakToken(std::move(next_column_token));
372   } else {
373     // We've gone through all the content. This doesn't necessarily mean that
374     // we're done fragmenting, since the multicol container may be taller than
375     // what the content requires, which means that we might create more
376     // (childless) fragments, if we're nested inside another fragmentation
377     // context. In that case we must make sure to skip the contents when
378     // resuming.
379     container_builder_.SetHasSeenAllChildren();
380 
381     intrinsic_block_size_ += margin_strut.Sum();
382   }
383 
384   return NGBreakStatus::kContinue;
385 }
386 
LayoutRow(const NGBlockBreakToken * next_column_token,NGMarginStrut * margin_strut)387 scoped_refptr<const NGLayoutResult> NGColumnLayoutAlgorithm::LayoutRow(
388     const NGBlockBreakToken* next_column_token,
389     NGMarginStrut* margin_strut) {
390   LogicalSize column_size(column_inline_size_, content_box_size_.block_size);
391 
392   // If block-size is non-auto, subtract the space for content we've consumed in
393   // previous fragments. This is necessary when we're nested inside another
394   // fragmentation context.
395   if (column_size.block_size != kIndefiniteSize) {
396     if (BreakToken() && is_constrained_by_outer_fragmentation_context_)
397       column_size.block_size -= BreakToken()->ConsumedBlockSize();
398 
399     // Subtract the space already taken in the current fragment (spanners and
400     // earlier column rows).
401     column_size.block_size -= CurrentContentBlockOffset();
402 
403     column_size.block_size = column_size.block_size.ClampNegativeToZero();
404   }
405 
406   // We balance if block-size is unconstrained, or when we're explicitly told
407   // to. Note that the block-size may be constrained by outer fragmentation
408   // contexts, not just by a block-size specified on this multicol container.
409   bool balance_columns = Style().GetColumnFill() == EColumnFill::kBalance ||
410                          (column_size.block_size == kIndefiniteSize &&
411                           !is_constrained_by_outer_fragmentation_context_);
412 
413   if (balance_columns) {
414     column_size.block_size =
415         CalculateBalancedColumnBlockSize(column_size, next_column_token);
416   }
417 
418   // Column rows have no representation in the DOM and have no margins, but
419   // there may be a trailing margin from a preceding spanner.
420   LayoutUnit column_block_offset = intrinsic_block_size_ + margin_strut->Sum();
421 
422   bool needs_more_fragments_in_outer = false;
423   bool zero_outer_space_left = false;
424   if (is_constrained_by_outer_fragmentation_context_) {
425     LayoutUnit available_outer_space =
426         FragmentainerSpaceAtBfcStart(ConstraintSpace()) - column_block_offset;
427 
428     if (available_outer_space <= LayoutUnit()) {
429       if (available_outer_space < LayoutUnit()) {
430         // We're past the end of the outer fragmentainer (typically due to a
431         // margin). Nothing will fit here, not even zero-size content.
432         return nullptr;
433       }
434 
435       // We are out of space, but we're exactly at the end of the outer
436       // fragmentainer. If none of our contents take up space, we're going to
437       // fit, otherwise not. Lay out and find out.
438       zero_outer_space_left = true;
439     }
440 
441     // Check if we can fit everything (that's remaining), block-wise, within the
442     // current outer fragmentainer. If we can't, we need to adjust the block
443     // size, and allow the multicol container to continue in a subsequent outer
444     // fragmentainer. Note that we also need to handle indefinite block-size,
445     // because that may happen in a nested multicol container with auto
446     // block-size and column balancing disabled.
447     if (column_size.block_size > available_outer_space ||
448         column_size.block_size == kIndefiniteSize) {
449       column_size.block_size = available_outer_space;
450       needs_more_fragments_in_outer = true;
451     }
452   }
453 
454   DCHECK_GE(column_size.block_size, LayoutUnit());
455 
456   // New column fragments won't be added to the fragment builder right away,
457   // since we may need to delete them and try again with a different block-size
458   // (colum balancing). Keep them in this list, and add them to the fragment
459   // builder when we have the final column fragments. Or clear the list and
460   // retry otherwise.
461   NGContainerFragmentBuilder::ChildrenVector new_columns;
462 
463   scoped_refptr<const NGLayoutResult> result;
464 
465   do {
466     scoped_refptr<const NGBlockBreakToken> column_break_token =
467         next_column_token;
468 
469     // This is the first column in this fragmentation context if there are no
470     // preceding columns in this row and there are also no preceding rows.
471     bool is_first_fragmentainer = !column_break_token && !BreakToken();
472 
473     LayoutUnit column_inline_offset(border_scrollbar_padding_.inline_start);
474     int actual_column_count = 0;
475     int forced_break_count = 0;
476 
477     // Each column should calculate their own minimal space shortage. Find the
478     // lowest value of those. This will serve as the column stretch amount, if
479     // we determine that stretching them is necessary and possible (column
480     // balancing).
481     LayoutUnit minimal_space_shortage(LayoutUnit::Max());
482 
483     do {
484       // Lay out one column. Each column will become a fragment.
485       NGConstraintSpace child_space = CreateConstraintSpaceForColumns(
486           column_size, is_first_fragmentainer, balance_columns);
487 
488       NGFragmentGeometry fragment_geometry =
489           CalculateInitialFragmentGeometry(child_space, Node());
490 
491       NGBlockLayoutAlgorithm child_algorithm(
492           {Node(), fragment_geometry, child_space, column_break_token.get()});
493       child_algorithm.SetBoxType(NGPhysicalFragment::kColumnBox);
494       result = child_algorithm.Layout();
495       const auto& column = result->PhysicalFragment();
496 
497       // Add the new column fragment to the list, but don't commit anything to
498       // the fragment builder until we know whether these are the final columns.
499       LogicalOffset logical_offset(column_inline_offset, column_block_offset);
500       new_columns.emplace_back(logical_offset, &result->PhysicalFragment());
501 
502       LayoutUnit space_shortage = result->MinimalSpaceShortage();
503       if (space_shortage > LayoutUnit()) {
504         minimal_space_shortage =
505             std::min(minimal_space_shortage, space_shortage);
506       }
507       actual_column_count++;
508       if (result->HasForcedBreak())
509         forced_break_count++;
510 
511       column_inline_offset += column_inline_progression_;
512 
513       if (result->ColumnSpanner())
514         break;
515 
516       column_break_token = To<NGBlockBreakToken>(column.BreakToken());
517 
518       // If we're participating in an outer fragmentation context, we'll only
519       // allow as many columns as the used value of column-count, so that we
520       // don't overflow in the inline direction. There's one important
521       // exception: If we have determined that this is going to be the last
522       // fragment for this multicol container in the outer fragmentation
523       // context, we'll just allow as many columns as needed (and let them
524       // overflow in the inline direction, if necessary). We're not going to
525       // progress into a next outer fragmentainer if the (remaining part of the)
526       // multicol container fits block-wise in the current outer fragmentainer.
527       if (ConstraintSpace().HasBlockFragmentation() && column_break_token &&
528           actual_column_count >= used_column_count_ &&
529           needs_more_fragments_in_outer) {
530         // We cannot keep any of this if we have zero space left. Then we need
531         // to resume in the next outer fragmentainer.
532         if (zero_outer_space_left)
533           return nullptr;
534 
535         container_builder_.SetDidBreak();
536         container_builder_.SetBreakAppeal(kBreakAppealPerfect);
537         break;
538       }
539 
540       is_first_fragmentainer = false;
541     } while (column_break_token);
542 
543     // TODO(mstensho): Nested column balancing.
544     if (container_builder_.DidBreak())
545       break;
546 
547     if (!balance_columns && result->ColumnSpanner()) {
548       // We always have to balance columns preceding a spanner, so if we didn't
549       // do that initially, switch over to column balancing mode now, and lay
550       // out again.
551       balance_columns = true;
552       new_columns.clear();
553       column_size.block_size =
554           CalculateBalancedColumnBlockSize(column_size, next_column_token);
555       continue;
556     }
557 
558     // If we overflowed (actual column count larger than what we have room for),
559     // and we're supposed to calculate the column lengths automatically (column
560     // balancing), see if we're able to stretch them.
561     //
562     // We can only stretch the columns if we have at least one column that could
563     // take more content, and we also need to know the stretch amount (minimal
564     // space shortage). We need at least one soft break opportunity to do
565     // this. If forced breaks cause too many breaks, there's no stretch amount
566     // that could prevent the actual column count from overflowing.
567     //
568     // TODO(mstensho): Handle this situation also when we're inside another
569     // balanced multicol container, rather than bailing (which we do now, to
570     // avoid infinite loops). If we exhaust the inner column-count in such
571     // cases, that piece of information may have to be propagated to the outer
572     // multicol, and instead stretch there (not here). We have no such mechanism
573     // in place yet.
574     if (balance_columns && actual_column_count > used_column_count_ &&
575         actual_column_count > forced_break_count + 1 &&
576         minimal_space_shortage != LayoutUnit::Max() &&
577         !ConstraintSpace().IsInsideBalancedColumns()) {
578       LayoutUnit new_column_block_size = StretchColumnBlockSize(
579           minimal_space_shortage, column_size.block_size);
580 
581       DCHECK_GE(new_column_block_size, column_size.block_size);
582       if (new_column_block_size > column_size.block_size) {
583         // Remove column fragments and re-attempt layout with taller columns.
584         new_columns.clear();
585         column_size.block_size = new_column_block_size;
586         continue;
587       }
588     }
589     break;
590   } while (true);
591 
592   bool is_empty = false;
593 
594   // If there was no content inside to process, we don't want the resulting
595   // empty column fragment.
596   if (new_columns.size() == 1u) {
597     const NGPhysicalBoxFragment& column =
598         *To<NGPhysicalBoxFragment>(new_columns[0].fragment.get());
599 
600     if (column.Children().size() == 0) {
601       // No content. Keep the trailing margin from any previous column spanner.
602       is_empty = true;
603 
604       // TODO(mstensho): It's wrong to keep the empty fragment, just so that
605       // out-of-flow descendants get propagated correctly. Find some other way
606       // of propagating them.
607       if (!column.HasOutOfFlowPositionedDescendants())
608         return result;
609     }
610   }
611 
612   intrinsic_block_size_ = column_block_offset + column_size.block_size;
613 
614   if (!is_empty) {
615     has_processed_first_child_ = true;
616     container_builder_.SetPreviousBreakAfter(EBreakBetween::kAuto);
617 
618     // We added a row. Reset the trailing margin from any previous column
619     // spanner.
620     *margin_strut = NGMarginStrut();
621   }
622 
623   // Commit all column fragments to the fragment builder.
624   for (auto column : new_columns) {
625     container_builder_.AddChild(To<NGPhysicalBoxFragment>(*column.fragment),
626                                 column.offset);
627   }
628 
629   return result;
630 }
631 
LayoutSpanner(NGBlockNode spanner_node,const NGBlockBreakToken * break_token,NGMarginStrut * margin_strut,scoped_refptr<const NGBlockBreakToken> * spanner_break_token)632 NGBreakStatus NGColumnLayoutAlgorithm::LayoutSpanner(
633     NGBlockNode spanner_node,
634     const NGBlockBreakToken* break_token,
635     NGMarginStrut* margin_strut,
636     scoped_refptr<const NGBlockBreakToken>* spanner_break_token) {
637   *spanner_break_token = nullptr;
638   const ComputedStyle& spanner_style = spanner_node.Style();
639   NGBoxStrut margins = ComputeMarginsFor(
640       spanner_style, content_box_size_.inline_size,
641       ConstraintSpace().GetWritingMode(), ConstraintSpace().Direction());
642 
643   if (break_token) {
644     // Truncate block-start margins at fragmentainer breaks (except when the
645     // break is forced), and also make sure that we don't repeat them at the
646     // beginning of every fragment generated from the spanner node.
647     if (!break_token->IsBreakBefore() || !break_token->IsForcedBreak())
648       margins.block_start = LayoutUnit();
649 
650     if (break_token->IsBreakBefore()) {
651       // TODO(mstensho): Passing a break-before token shouldn't be a problem,
652       // but it would cause problems for the NGPaintFragment code. Just pass
653       // nullptr. Won't make any difference anyway.
654       break_token = nullptr;
655     }
656   }
657 
658   // Collapse the block-start margin of this spanner with the block-end margin
659   // of an immediately preceding spanner, if any.
660   margin_strut->Append(margins.block_start, /* is_quirky */ false);
661 
662   LayoutUnit block_offset = intrinsic_block_size_ + margin_strut->Sum();
663   auto spanner_space =
664       CreateConstraintSpaceForSpanner(spanner_node, block_offset);
665 
666   const NGEarlyBreak* early_break_in_child = nullptr;
667   if (early_break_ && early_break_->Type() == NGEarlyBreak::kBlock &&
668       early_break_->BlockNode() == spanner_node) {
669     // We're entering a child that we know that we're going to break inside, and
670     // even where to break. Look inside, and pass the inner breakpoint to
671     // layout.
672     early_break_in_child = early_break_->BreakInside();
673     // If there's no break inside, we should already have broken before this
674     // child.
675     DCHECK(early_break_in_child);
676   }
677 
678   auto result =
679       spanner_node.Layout(spanner_space, break_token, early_break_in_child);
680 
681   if (ConstraintSpace().HasBlockFragmentation() && !early_break_) {
682     // We're nested inside another fragmentation context. Examine this break
683     // point, and determine whether we should break.
684 
685     LayoutUnit fragmentainer_block_offset =
686         ConstraintSpace().FragmentainerOffsetAtBfc() + block_offset;
687 
688     NGBreakStatus break_status = BreakBeforeChildIfNeeded(
689         ConstraintSpace(), spanner_node, *result.get(),
690         fragmentainer_block_offset, has_processed_first_child_,
691         &container_builder_);
692 
693     if (break_status != NGBreakStatus::kContinue) {
694       // We need to break, either before the spanner, or even earlier.
695       return break_status;
696     }
697   }
698 
699   NGFragment fragment(ConstraintSpace().GetWritingMode(),
700                       result->PhysicalFragment());
701 
702   ResolveInlineMargins(spanner_style, Style(), content_box_size_.inline_size,
703                        fragment.InlineSize(), &margins);
704 
705   LogicalOffset offset(
706       border_scrollbar_padding_.inline_start + margins.inline_start,
707       block_offset);
708   container_builder_.AddResult(*result, offset);
709 
710   *margin_strut = NGMarginStrut();
711   margin_strut->Append(margins.block_end, /* is_quirky */ false);
712 
713   intrinsic_block_size_ = offset.block_offset + fragment.BlockSize();
714   has_processed_first_child_ = true;
715 
716   EBreakBetween break_after = JoinFragmentainerBreakValues(
717       result->FinalBreakAfter(), spanner_node.Style().BreakAfter());
718   container_builder_.SetPreviousBreakAfter(break_after);
719 
720   *spanner_break_token =
721       To<NGBlockBreakToken>(result->PhysicalFragment().BreakToken());
722   return NGBreakStatus::kContinue;
723 }
724 
CalculateBalancedColumnBlockSize(const LogicalSize & column_size,const NGBlockBreakToken * child_break_token)725 LayoutUnit NGColumnLayoutAlgorithm::CalculateBalancedColumnBlockSize(
726     const LogicalSize& column_size,
727     const NGBlockBreakToken* child_break_token) {
728   // To calculate a balanced column size for one row of columns, we need to
729   // figure out how tall our content is. To do that we need to lay out. Create a
730   // special constraint space for column balancing, without allowing soft
731   // breaks. It will make us lay out all the multicol content as one single tall
732   // strip (unless there are forced breaks). When we're done with this layout
733   // pass, we can examine the result and calculate an ideal column block-size.
734   NGConstraintSpace space = CreateConstraintSpaceForBalancing(column_size);
735   NGFragmentGeometry fragment_geometry =
736       CalculateInitialFragmentGeometry(space, Node());
737 
738   // A run of content without explicit (forced) breaks; i.e. the content portion
739   // between two explicit breaks, between fragmentation context start and an
740   // explicit break, between an explicit break and fragmentation context end,
741   // or, in cases when there are no explicit breaks at all: between
742   // fragmentation context start and end. We need to know where the explicit
743   // breaks are, in order to figure out where the implicit breaks will end up,
744   // so that we get the columns properly balanced. A content run starts out as
745   // representing one single column, and we'll add as many additional implicit
746   // breaks as needed into the content runs that are the tallest ones
747   // (ColumnBlockSize()).
748   struct ContentRun {
749     ContentRun(LayoutUnit content_block_size)
750         : content_block_size(content_block_size) {}
751 
752     // Return the column block-size that this content run would require,
753     // considering the implicit breaks we have assumed so far.
754     LayoutUnit ColumnBlockSize() const {
755       // Some extra care is required for the division here. We want the
756       // resulting LayoutUnit value to be large enough to prevent overflowing
757       // columns. Use floating point to get higher precision than
758       // LayoutUnit. Then convert it to a LayoutUnit, but round it up to the
759       // nearest value that LayoutUnit is able to represent.
760       return LayoutUnit::FromFloatCeil(
761           float(content_block_size) / float(implicit_breaks_assumed_count + 1));
762     }
763 
764     LayoutUnit content_block_size;
765 
766     // The number of implicit breaks assumed to exist in this content run.
767     int implicit_breaks_assumed_count = 0;
768   };
769 
770   class ContentRuns : public Vector<ContentRun, 1> {
771    public:
772     wtf_size_t IndexWithTallestColumns() const {
773       DCHECK_GT(size(), 0u);
774       wtf_size_t index = 0;
775       LayoutUnit largest_block_size = LayoutUnit::Min();
776       for (size_t i = 0; i < size(); i++) {
777         const ContentRun& run = at(i);
778         LayoutUnit block_size = run.ColumnBlockSize();
779         if (largest_block_size < block_size) {
780           largest_block_size = block_size;
781           index = i;
782         }
783       }
784       return index;
785     }
786 
787     // When we have "inserted" (assumed) enough implicit column breaks, this
788     // method returns the block-size of the tallest column.
789     LayoutUnit TallestColumnBlockSize() const {
790       return at(IndexWithTallestColumns()).ColumnBlockSize();
791     }
792   };
793 
794   // First split into content runs at explicit (forced) breaks.
795   ContentRuns content_runs;
796   scoped_refptr<const NGBlockBreakToken> break_token = child_break_token;
797   LayoutUnit tallest_unbreakable_block_size;
798   do {
799     NGBlockLayoutAlgorithm balancing_algorithm(
800         {Node(), fragment_geometry, space, break_token.get()});
801     scoped_refptr<const NGLayoutResult> result = balancing_algorithm.Layout();
802 
803     // This algorithm should never abort.
804     DCHECK_EQ(result->Status(), NGLayoutResult::kSuccess);
805 
806     const NGPhysicalBoxFragment& fragment =
807         To<NGPhysicalBoxFragment>(result->PhysicalFragment());
808     LayoutUnit column_block_size = CalculateColumnContentBlockSize(
809         fragment, IsHorizontalWritingMode(space.GetWritingMode()));
810     content_runs.emplace_back(column_block_size);
811 
812     tallest_unbreakable_block_size = std::max(
813         tallest_unbreakable_block_size, result->TallestUnbreakableBlockSize());
814 
815     // Stop when we reach a spanner. That's where this row of columns will end.
816     if (result->ColumnSpanner())
817       break;
818 
819     break_token = To<NGBlockBreakToken>(fragment.BreakToken());
820   } while (break_token);
821 
822   // Then distribute as many implicit breaks into the content runs as we need.
823   int used_column_count =
824       ResolveUsedColumnCount(content_box_size_.inline_size, Style());
825   for (int columns_found = content_runs.size();
826        columns_found < used_column_count; columns_found++) {
827     // The tallest content run (with all assumed implicit breaks added so far
828     // taken into account) is where we assume the next implicit break.
829     wtf_size_t index = content_runs.IndexWithTallestColumns();
830     content_runs[index].implicit_breaks_assumed_count++;
831   }
832 
833   if (ConstraintSpace().IsInitialColumnBalancingPass()) {
834     // Nested column balancing. Our outer fragmentation context is in its
835     // initial balancing pass, so it also wants to know the largest unbreakable
836     // block-size.
837     container_builder_.PropagateTallestUnbreakableBlockSize(
838         tallest_unbreakable_block_size);
839   }
840 
841   // We now have an estimated minimal block-size for the columns. Roughly
842   // speaking, this is the block-size that the columns will need if we are
843   // allowed to break freely at any offset. This is normally not the case,
844   // though, since there will typically be unbreakable pieces of content, such
845   // as replaced content, lines of text, and other things. We need to actually
846   // lay out into columns to figure out if they are tall enough or not (and
847   // stretch and retry if not). Also honor {,min-,max-}{height,width} properties
848   // before returning.
849   LayoutUnit block_size = std::max(content_runs.TallestColumnBlockSize(),
850                                    tallest_unbreakable_block_size);
851 
852   return ConstrainColumnBlockSize(block_size);
853 }
854 
StretchColumnBlockSize(LayoutUnit minimal_space_shortage,LayoutUnit current_column_size) const855 LayoutUnit NGColumnLayoutAlgorithm::StretchColumnBlockSize(
856     LayoutUnit minimal_space_shortage,
857     LayoutUnit current_column_size) const {
858   LayoutUnit length = current_column_size + minimal_space_shortage;
859   // Honor {,min-,max-}{height,width} properties.
860   return ConstrainColumnBlockSize(length);
861 }
862 
863 // Constrain a balanced column block size to not overflow the multicol
864 // container.
ConstrainColumnBlockSize(LayoutUnit size) const865 LayoutUnit NGColumnLayoutAlgorithm::ConstrainColumnBlockSize(
866     LayoutUnit size) const {
867   // The {,max-}{height,width} properties are specified on the multicol
868   // container, but here we're calculating the column block sizes inside the
869   // multicol container, which isn't exactly the same. We may shrink the column
870   // block size here, but we'll never stretch it, because the value passed is
871   // the perfect balanced block size. Making it taller would only disrupt the
872   // balanced output, for no reason. The only thing we need to worry about here
873   // is to not overflow the multicol container.
874 
875   // First of all we need to convert the size to a value that can be compared
876   // against the resolved properties on the multicol container. That means that
877   // we have to convert the value from content-box to border-box.
878   LayoutUnit extra = border_scrollbar_padding_.BlockSum();
879   size += extra;
880 
881   const ComputedStyle& style = Style();
882   LayoutUnit max = ResolveMaxBlockLength(
883       ConstraintSpace(), style, border_padding_, style.LogicalMaxHeight(),
884       LengthResolvePhase::kLayout);
885   LayoutUnit extent = ResolveMainBlockLength(
886       ConstraintSpace(), style, border_padding_, style.LogicalHeight(), size,
887       LengthResolvePhase::kLayout);
888   if (extent != kIndefiniteSize) {
889     // A specified height/width will just constrain the maximum length.
890     max = std::min(max, extent);
891   }
892 
893   // Constrain and convert the value back to content-box.
894   size = std::min(size, max);
895   return size - extra;
896 }
897 
FinishAfterBreakBeforeRow(scoped_refptr<const NGBlockBreakToken> next_column_token)898 void NGColumnLayoutAlgorithm::FinishAfterBreakBeforeRow(
899     scoped_refptr<const NGBlockBreakToken> next_column_token) {
900   // We broke before a row for columns. We're done here. Take up the remaining
901   // space in the outer fragmentation context.
902   intrinsic_block_size_ = FragmentainerSpaceAtBfcStart(ConstraintSpace());
903 
904   // If we were about to resume column layout after a spanner, add a break token
905   // for this, so that we resume there in the next outer fragmentainer. If
906   // there's no such break token, it means that we're at the start of the
907   // multicol container.
908   if (next_column_token)
909     container_builder_.AddBreakToken(std::move(next_column_token));
910 }
911 
FinishAfterBreakBeforeSpanner(scoped_refptr<const NGBlockBreakToken> next_column_token)912 void NGColumnLayoutAlgorithm::FinishAfterBreakBeforeSpanner(
913     scoped_refptr<const NGBlockBreakToken> next_column_token) {
914   // We broke before the spanner. We're done here. Take up the remaining space
915   // in the outer fragmentation context.
916   intrinsic_block_size_ = FragmentainerSpaceAtBfcStart(ConstraintSpace());
917 
918   // A break token for the spanner has already been inserted, but we also need
919   // to add one for the column contents that follows, so that we know where to
920   // resume, once done with the spanner - or - specify that we're past
921   // everything if there's nothing to resume at (so that we don't restart from
922   // the beginning of the multicol container).
923   PushNextColumnBreakToken(std::move(next_column_token), &container_builder_);
924 }
925 
926 scoped_refptr<const NGLayoutResult>
RelayoutAndBreakEarlier()927 NGColumnLayoutAlgorithm::RelayoutAndBreakEarlier() {
928   // Not allowed to recurse!
929   DCHECK(!early_break_);
930 
931   const NGEarlyBreak& breakpoint = container_builder_.EarlyBreak();
932   NGLayoutAlgorithmParams params(Node(),
933                                  container_builder_.InitialFragmentGeometry(),
934                                  ConstraintSpace(), BreakToken(), &breakpoint);
935   NGColumnLayoutAlgorithm algorithm_with_break(params);
936   NGBoxFragmentBuilder& new_builder = algorithm_with_break.container_builder_;
937   new_builder.SetBoxType(container_builder_.BoxType());
938   // We're not going to run out of space in the next layout pass, since we're
939   // breaking earlier, so no space shortage will be detected. Repeat what we
940   // found in this pass.
941   new_builder.PropagateSpaceShortage(container_builder_.MinimalSpaceShortage());
942   return algorithm_with_break.Layout();
943 }
944 
CreateConstraintSpaceForColumns(const LogicalSize & column_size,bool is_first_fragmentainer,bool balance_columns) const945 NGConstraintSpace NGColumnLayoutAlgorithm::CreateConstraintSpaceForColumns(
946     const LogicalSize& column_size,
947     bool is_first_fragmentainer,
948     bool balance_columns) const {
949   NGConstraintSpaceBuilder space_builder(
950       ConstraintSpace(), Style().GetWritingMode(), /* is_new_fc */ true);
951   space_builder.SetAvailableSize(column_size);
952   space_builder.SetPercentageResolutionSize(column_size);
953 
954   // To ensure progression, we need something larger than 0 here. The spec
955   // actually says that fragmentainers have to accept at least 1px of content.
956   // See https://www.w3.org/TR/css-break-3/#breaking-rules
957   LayoutUnit column_block_size =
958       std::max(column_size.block_size, LayoutUnit(1));
959 
960   space_builder.SetFragmentationType(kFragmentColumn);
961   space_builder.SetFragmentainerBlockSize(column_block_size);
962   space_builder.SetIsAnonymous(true);
963   space_builder.SetIsInColumnBfc();
964   if (balance_columns)
965     space_builder.SetIsInsideBalancedColumns();
966   if (!is_first_fragmentainer) {
967     // Margins at fragmentainer boundaries should be eaten and truncated to
968     // zero. Note that this doesn't apply to margins at forced breaks, but we'll
969     // deal with those when we get to them. Set up a margin strut that eats all
970     // leading adjacent margins.
971     space_builder.SetDiscardingMarginStrut();
972   }
973 
974   return space_builder.ToConstraintSpace();
975 }
976 
CreateConstraintSpaceForBalancing(const LogicalSize & column_size) const977 NGConstraintSpace NGColumnLayoutAlgorithm::CreateConstraintSpaceForBalancing(
978     const LogicalSize& column_size) const {
979   NGConstraintSpaceBuilder space_builder(
980       ConstraintSpace(), Style().GetWritingMode(), /* is_new_fc */ true);
981   space_builder.SetFragmentationType(kFragmentColumn);
982   space_builder.SetAvailableSize({column_size.inline_size, kIndefiniteSize});
983   space_builder.SetPercentageResolutionSize(column_size);
984   space_builder.SetIsAnonymous(true);
985   space_builder.SetIsInColumnBfc();
986   space_builder.SetIsInsideBalancedColumns();
987 
988   return space_builder.ToConstraintSpace();
989 }
990 
CreateConstraintSpaceForSpanner(const NGBlockNode & spanner,LayoutUnit block_offset) const991 NGConstraintSpace NGColumnLayoutAlgorithm::CreateConstraintSpaceForSpanner(
992     const NGBlockNode& spanner,
993     LayoutUnit block_offset) const {
994   NGConstraintSpaceBuilder space_builder(
995       ConstraintSpace(), Style().GetWritingMode(), /* is_new_fc */ true);
996   space_builder.SetAvailableSize(content_box_size_);
997   space_builder.SetPercentageResolutionSize(content_box_size_);
998 
999   if (ConstraintSpace().HasBlockFragmentation()) {
1000     SetupFragmentation(ConstraintSpace(), spanner, block_offset, &space_builder,
1001                        /* is_new_fc */ true);
1002   }
1003 
1004   return space_builder.ToConstraintSpace();
1005 }
1006 
CreateConstraintSpaceForMinMax() const1007 NGConstraintSpace NGColumnLayoutAlgorithm::CreateConstraintSpaceForMinMax()
1008     const {
1009   NGConstraintSpaceBuilder space_builder(
1010       ConstraintSpace(), Style().GetWritingMode(), /* is_new_fc */ true);
1011   space_builder.SetIsAnonymous(true);
1012   space_builder.SetIsInColumnBfc();
1013 
1014   return space_builder.ToConstraintSpace();
1015 }
1016 
1017 }  // namespace blink
1018