1 // Copyright 2015 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.
7 #include "core/layout/MultiColumnFragmentainerGroup.h"
9 #include "core/layout/LayoutMultiColumnSet.h"
13 MultiColumnFragmentainerGroup::MultiColumnFragmentainerGroup(LayoutMultiColumnSet
& columnSet
)
14 : m_columnSet(columnSet
)
18 bool MultiColumnFragmentainerGroup::isLastGroup() const
20 return &m_columnSet
.lastFragmentainerGroup() == this;
23 LayoutSize
MultiColumnFragmentainerGroup::offsetFromColumnSet() const
25 LayoutSize
offset(LayoutUnit(), logicalTop());
26 if (!m_columnSet
.flowThread()->isHorizontalWritingMode())
27 return offset
.transposedSize();
31 LayoutUnit
MultiColumnFragmentainerGroup::blockOffsetInEnclosingFlowThread() const
33 return logicalTop() + m_columnSet
.logicalTop() + m_columnSet
.multiColumnFlowThread()->blockOffsetInEnclosingFlowThread();
36 bool MultiColumnFragmentainerGroup::heightIsAuto() const
38 // Only the last row may have auto height, and thus be balanced. There are no good reasons to
39 // balance the preceding rows, and that could potentially lead to an insane number of layout
41 return isLastGroup() && m_columnSet
.heightIsAuto();
44 void MultiColumnFragmentainerGroup::resetColumnHeight()
46 // Nuke previously stored minimum column height. Contents may have changed for all we know.
47 m_minimumColumnHeight
= 0;
49 m_maxColumnHeight
= calculateMaxColumnHeight();
51 LayoutUnit oldColumnHeight
= m_columnHeight
;
53 LayoutMultiColumnFlowThread
* flowThread
= m_columnSet
.multiColumnFlowThread();
54 LayoutMultiColumnFlowThread
* enclosingFlowThread
= flowThread
->enclosingFlowThread();
55 if (enclosingFlowThread
&& enclosingFlowThread
->isPageLogicalHeightKnown()) {
56 // TODO(mstensho): Do this better. If height is auto here, we shouldn't set a
57 // height, or forced breaks and pagination struts might mess up column balancing.
58 LayoutUnit columnHeight
= heightIsAuto() ? m_maxColumnHeight
: heightAdjustedForRowOffset(flowThread
->columnHeightAvailable());
59 setAndConstrainColumnHeight(columnHeight
);
60 } else if (heightIsAuto()) {
61 m_columnHeight
= LayoutUnit();
63 setAndConstrainColumnHeight(heightAdjustedForRowOffset(flowThread
->columnHeightAvailable()));
66 if (m_columnHeight
!= oldColumnHeight
)
67 m_columnSet
.setChildNeedsLayout(MarkOnlyThis
);
69 // Content runs are only needed in the initial layout pass, in order to find an initial column
70 // height, and should have been deleted afterwards. We're about to rebuild the content runs, so
71 // the list needs to be empty.
72 ASSERT(m_contentRuns
.isEmpty());
75 void MultiColumnFragmentainerGroup::addContentRun(LayoutUnit endOffsetInFlowThread
)
77 if (!m_contentRuns
.isEmpty() && endOffsetInFlowThread
<= m_contentRuns
.last().breakOffset())
79 // Append another item as long as we haven't exceeded used column count. What ends up in the
80 // overflow area shouldn't affect column balancing.
81 if (m_contentRuns
.size() < m_columnSet
.usedColumnCount())
82 m_contentRuns
.append(ContentRun(endOffsetInFlowThread
));
85 void MultiColumnFragmentainerGroup::recordSpaceShortage(LayoutUnit spaceShortage
)
87 if (spaceShortage
>= m_minSpaceShortage
)
90 // The space shortage is what we use as our stretch amount. We need a positive number here in
91 // order to get anywhere.
92 ASSERT(spaceShortage
> 0);
94 m_minSpaceShortage
= spaceShortage
;
97 bool MultiColumnFragmentainerGroup::recalculateColumnHeight(BalancedColumnHeightCalculation calculationMode
)
99 LayoutUnit oldColumnHeight
= m_columnHeight
;
101 m_maxColumnHeight
= calculateMaxColumnHeight();
103 if (heightIsAuto()) {
104 if (calculationMode
== GuessFromFlowThreadPortion
) {
105 // Post-process the content runs and find out where the implicit breaks will occur.
106 distributeImplicitBreaks();
108 LayoutUnit newColumnHeight
= calculateColumnHeight(calculationMode
);
109 setAndConstrainColumnHeight(newColumnHeight
);
110 // After having calculated an initial column height, the multicol container typically needs at
111 // least one more layout pass with a new column height, but if a height was specified, we only
112 // need to do this if we think that we need less space than specified. Conversely, if we
113 // determined that the columns need to be as tall as the specified height of the container, we
114 // have already laid it out correctly, and there's no need for another pass.
116 // The position of the column set may have changed, in which case height available for
117 // columns may have changed as well.
118 setAndConstrainColumnHeight(m_columnHeight
);
121 // We can get rid of the content runs now, if we haven't already done so. They are only needed
122 // to calculate the initial balanced column height. In fact, we have to get rid of them before
123 // the next layout pass, since each pass will rebuild this.
124 m_contentRuns
.clear();
126 if (m_columnHeight
== oldColumnHeight
)
127 return false; // No change. We're done.
129 m_minSpaceShortage
= LayoutUnit::max();
130 return true; // Need another pass.
133 LayoutSize
MultiColumnFragmentainerGroup::flowThreadTranslationAtOffset(LayoutUnit offsetInFlowThread
) const
135 LayoutMultiColumnFlowThread
* flowThread
= m_columnSet
.multiColumnFlowThread();
136 unsigned columnIndex
= columnIndexAtOffset(offsetInFlowThread
);
137 LayoutRect
portionRect(flowThreadPortionRectAt(columnIndex
));
138 flowThread
->flipForWritingMode(portionRect
);
139 LayoutRect
columnRect(columnRectAt(columnIndex
));
140 m_columnSet
.flipForWritingMode(columnRect
);
141 LayoutSize translationRelativeToGroup
= columnRect
.location() - portionRect
.location();
143 LayoutSize enclosingTranslation
;
144 if (LayoutMultiColumnFlowThread
* enclosingFlowThread
= flowThread
->enclosingFlowThread()) {
145 // Translation that would map points in the coordinate space of the outermost flow thread to
146 // visual points in the first column in the first fragmentainer group (row) in our multicol
148 LayoutSize enclosingTranslationOrigin
= enclosingFlowThread
->flowThreadTranslationAtOffset(flowThread
->blockOffsetInEnclosingFlowThread());
150 // Translation that would map points in the coordinate space of the outermost flow thread to
151 // visual points in the first column in this fragmentainer group.
152 enclosingTranslation
= enclosingFlowThread
->flowThreadTranslationAtOffset(blockOffsetInEnclosingFlowThread());
154 // What we ultimately return from this method is a translation that maps points in the
155 // coordinate space of our flow thread to a visual point in a certain column in this
156 // fragmentainer group. We had to go all the way up to the outermost flow thread, since this
157 // fragmentainer group may be in a different outer column than the first outer column that
158 // this multicol container lives in. It's the visual distance between the first
159 // fragmentainer group and this fragmentainer group that we need to add to the translation.
160 enclosingTranslation
-= enclosingTranslationOrigin
;
163 return enclosingTranslation
+ translationRelativeToGroup
+ offsetFromColumnSet() + m_columnSet
.topLeftLocationOffset() - flowThread
->topLeftLocationOffset();
166 LayoutUnit
MultiColumnFragmentainerGroup::columnLogicalTopForOffset(LayoutUnit offsetInFlowThread
) const
168 unsigned columnIndex
= columnIndexAtOffset(offsetInFlowThread
, AssumeNewColumns
);
169 return logicalTopInFlowThreadAt(columnIndex
);
172 LayoutPoint
MultiColumnFragmentainerGroup::visualPointToFlowThreadPoint(const LayoutPoint
& visualPoint
) const
174 unsigned columnIndex
= columnIndexAtVisualPoint(visualPoint
);
175 LayoutRect columnRect
= columnRectAt(columnIndex
);
176 LayoutPoint
localPoint(visualPoint
);
177 localPoint
.moveBy(-columnRect
.location());
178 // Before converting to a flow thread position, if the block direction coordinate is outside the
179 // column, snap to the bounds of the column, and reset the inline direction coordinate to the
180 // start position in the column. The effect of this is that if the block position is before the
181 // column rectangle, we'll get to the beginning of this column, while if the block position is
182 // after the column rectangle, we'll get to the beginning of the next column.
183 if (!m_columnSet
.isHorizontalWritingMode()) {
184 LayoutUnit columnStart
= m_columnSet
.style()->isLeftToRightDirection() ? LayoutUnit() : columnRect
.height();
185 if (localPoint
.x() < 0)
186 localPoint
= LayoutPoint(LayoutUnit(), columnStart
);
187 else if (localPoint
.x() > logicalHeight())
188 localPoint
= LayoutPoint(logicalHeight(), columnStart
);
189 return LayoutPoint(localPoint
.x() + logicalTopInFlowThreadAt(columnIndex
), localPoint
.y());
191 LayoutUnit columnStart
= m_columnSet
.style()->isLeftToRightDirection() ? LayoutUnit() : columnRect
.width();
192 if (localPoint
.y() < 0)
193 localPoint
= LayoutPoint(columnStart
, LayoutUnit());
194 else if (localPoint
.y() > logicalHeight())
195 localPoint
= LayoutPoint(columnStart
, logicalHeight());
196 return LayoutPoint(localPoint
.x(), localPoint
.y() + logicalTopInFlowThreadAt(columnIndex
));
199 LayoutRect
MultiColumnFragmentainerGroup::fragmentsBoundingBox(const LayoutRect
& boundingBoxInFlowThread
) const
201 // Find the start and end column intersected by the bounding box.
202 LayoutRect
flippedBoundingBoxInFlowThread(boundingBoxInFlowThread
);
203 LayoutFlowThread
* flowThread
= m_columnSet
.flowThread();
204 flowThread
->flipForWritingMode(flippedBoundingBoxInFlowThread
);
205 bool isHorizontalWritingMode
= m_columnSet
.isHorizontalWritingMode();
206 LayoutUnit boundingBoxLogicalTop
= isHorizontalWritingMode
? flippedBoundingBoxInFlowThread
.y() : flippedBoundingBoxInFlowThread
.x();
207 LayoutUnit boundingBoxLogicalBottom
= isHorizontalWritingMode
? flippedBoundingBoxInFlowThread
.maxY() : flippedBoundingBoxInFlowThread
.maxX();
208 if (boundingBoxLogicalBottom
<= logicalTopInFlowThread() || boundingBoxLogicalTop
>= logicalBottomInFlowThread())
209 return LayoutRect(); // The bounding box doesn't intersect this fragmentainer group.
210 unsigned startColumn
;
212 columnIntervalForBlockRangeInFlowThread(boundingBoxLogicalTop
, boundingBoxLogicalBottom
, startColumn
, endColumn
);
214 LayoutRect startColumnFlowThreadOverflowPortion
= flowThreadPortionOverflowRectAt(startColumn
);
215 flowThread
->flipForWritingMode(startColumnFlowThreadOverflowPortion
);
216 LayoutRect
startColumnRect(boundingBoxInFlowThread
);
217 startColumnRect
.intersect(startColumnFlowThreadOverflowPortion
);
218 startColumnRect
.move(flowThreadTranslationAtOffset(logicalTopInFlowThreadAt(startColumn
)));
219 if (startColumn
== endColumn
)
220 return startColumnRect
; // It all takes place in one column. We're done.
222 LayoutRect endColumnFlowThreadOverflowPortion
= flowThreadPortionOverflowRectAt(endColumn
);
223 flowThread
->flipForWritingMode(endColumnFlowThreadOverflowPortion
);
224 LayoutRect
endColumnRect(boundingBoxInFlowThread
);
225 endColumnRect
.intersect(endColumnFlowThreadOverflowPortion
);
226 endColumnRect
.move(flowThreadTranslationAtOffset(logicalTopInFlowThreadAt(endColumn
)));
227 return unionRect(startColumnRect
, endColumnRect
);
230 void MultiColumnFragmentainerGroup::collectLayerFragments(DeprecatedPaintLayerFragments
& fragments
, const LayoutRect
& layerBoundingBox
, const LayoutRect
& dirtyRect
) const
232 // |layerBoundingBox| is in the flow thread coordinate space, relative to the top/left edge of
233 // the flow thread, but note that it has been converted with respect to writing mode (so that
234 // it's visual/physical in that sense).
236 // |dirtyRect| is visual, relative to the multicol container.
238 // Then there's the output from this method - the stuff we put into the list of fragments. The
239 // fragment.paginationOffset point is the actual visual translation required to get from a
240 // location in the flow thread to a location in a given column. The fragment.paginationClip
241 // rectangle, on the other hand, is in flow thread coordinates, but otherwise completely
242 // physical in terms of writing mode.
244 LayoutMultiColumnFlowThread
* flowThread
= m_columnSet
.multiColumnFlowThread();
245 bool isHorizontalWritingMode
= m_columnSet
.isHorizontalWritingMode();
247 // Put the layer bounds into flow thread-local coordinates by flipping it first. Since we're in
248 // a layoutObject, most rectangles are represented this way.
249 LayoutRect
layerBoundsInFlowThread(layerBoundingBox
);
250 flowThread
->flipForWritingMode(layerBoundsInFlowThread
);
252 // Now we can compare with the flow thread portions owned by each column. First let's
253 // see if the rect intersects our flow thread portion at all.
254 LayoutRect
clippedRect(layerBoundsInFlowThread
);
255 clippedRect
.intersect(m_columnSet
.flowThreadPortionOverflowRect());
256 if (clippedRect
.isEmpty())
259 // Now we know we intersect at least one column. Let's figure out the logical top and logical
260 // bottom of the area we're checking.
261 LayoutUnit layerLogicalTop
= isHorizontalWritingMode
? layerBoundsInFlowThread
.y() : layerBoundsInFlowThread
.x();
262 LayoutUnit layerLogicalBottom
= (isHorizontalWritingMode
? layerBoundsInFlowThread
.maxY() : layerBoundsInFlowThread
.maxX());
264 // Figure out the start and end columns for the layer and only check within that range so that
265 // we don't walk the entire column row.
266 unsigned startColumn
;
268 columnIntervalForBlockRangeInFlowThread(layerLogicalTop
, layerLogicalBottom
, startColumn
, endColumn
);
270 // Now intersect with the columns actually occupied by the dirty rect, to narrow it down even further.
271 unsigned firstColumnInDirtyRect
, lastColumnInDirtyRect
;
272 columnIntervalForVisualRect(dirtyRect
, firstColumnInDirtyRect
, lastColumnInDirtyRect
);
273 if (firstColumnInDirtyRect
> endColumn
|| lastColumnInDirtyRect
< startColumn
)
274 return; // The two column intervals are disjoint. There's nothing to collect.
275 if (startColumn
< firstColumnInDirtyRect
)
276 startColumn
= firstColumnInDirtyRect
;
277 if (endColumn
> lastColumnInDirtyRect
)
278 endColumn
= lastColumnInDirtyRect
;
279 ASSERT(endColumn
>= startColumn
);
281 for (unsigned i
= startColumn
; i
<= endColumn
; i
++) {
282 DeprecatedPaintLayerFragment fragment
;
284 // Set the physical translation offset.
285 fragment
.paginationOffset
= toLayoutPoint(flowThreadTranslationAtOffset(logicalTopInFlowThreadAt(i
)));
287 // Set the overflow clip rect that corresponds to the column.
288 fragment
.paginationClip
= flowThreadPortionOverflowRectAt(i
);
289 // Flip it into more a physical (DeprecatedPaintLayer-style) rectangle.
290 flowThread
->flipForWritingMode(fragment
.paginationClip
);
292 fragments
.append(fragment
);
296 LayoutRect
MultiColumnFragmentainerGroup::calculateOverflow() const
298 unsigned columnCount
= actualColumnCount();
301 return columnRectAt(columnCount
- 1);
304 unsigned MultiColumnFragmentainerGroup::actualColumnCount() const
306 // We must always return a value of 1 or greater. Column count = 0 is a meaningless situation,
307 // and will confuse and cause problems in other parts of the code.
311 // Our flow thread portion determines our column count. We have as many columns as needed to fit
313 LayoutUnit flowThreadPortionHeight
= logicalHeightInFlowThread();
314 if (!flowThreadPortionHeight
)
317 unsigned count
= (flowThreadPortionHeight
/ m_columnHeight
).floor();
318 // flowThreadPortionHeight may be saturated, so detect the remainder manually.
319 if (count
* m_columnHeight
< flowThreadPortionHeight
)
325 LayoutUnit
MultiColumnFragmentainerGroup::heightAdjustedForRowOffset(LayoutUnit height
) const
327 // Adjust for the top offset within the content box of the multicol container (containing
328 // block), unless we're in the first set. We know that the top offset for the first set will be
329 // zero, but if the multicol container has non-zero top border or padding, the set's top offset
330 // (initially being 0 and relative to the border box) will be negative until it has been laid
331 // out. Had we used this bogus offset, we would calculate the wrong height, and risk performing
332 // a wasted layout iteration. Of course all other sets (if any) have this problem in the first
333 // layout pass too, but there's really nothing we can do there until the flow thread has been
335 if (m_columnSet
.previousSiblingMultiColumnSet()) {
336 LayoutBlockFlow
* multicolBlock
= m_columnSet
.multiColumnBlockFlow();
337 LayoutUnit contentLogicalTop
= m_columnSet
.logicalTop() - multicolBlock
->borderAndPaddingBefore();
338 height
-= contentLogicalTop
;
340 height
-= logicalTop();
341 return max(height
, LayoutUnit(1)); // Let's avoid zero height, as that would probably cause an infinite amount of columns to be created.
344 LayoutUnit
MultiColumnFragmentainerGroup::calculateMaxColumnHeight() const
346 LayoutBlockFlow
* multicolBlock
= m_columnSet
.multiColumnBlockFlow();
347 const ComputedStyle
& multicolStyle
= multicolBlock
->styleRef();
348 LayoutMultiColumnFlowThread
* flowThread
= m_columnSet
.multiColumnFlowThread();
349 LayoutUnit availableHeight
= flowThread
->columnHeightAvailable();
350 LayoutUnit maxColumnHeight
= availableHeight
? availableHeight
: LayoutUnit::max();
351 if (!multicolStyle
.logicalMaxHeight().isMaxSizeNone()) {
352 LayoutUnit logicalMaxHeight
= multicolBlock
->computeContentLogicalHeight(MaxSize
, multicolStyle
.logicalMaxHeight(), -1);
353 if (logicalMaxHeight
!= -1 && maxColumnHeight
> logicalMaxHeight
)
354 maxColumnHeight
= logicalMaxHeight
;
356 LayoutUnit maxHeight
= heightAdjustedForRowOffset(maxColumnHeight
);
357 if (LayoutMultiColumnFlowThread
* enclosingFlowThread
= flowThread
->enclosingFlowThread()) {
358 if (enclosingFlowThread
->isPageLogicalHeightKnown()) {
359 // We're nested inside another fragmentation context whose fragmentainer heights are
360 // known. This constrains the max height.
361 LayoutUnit remainingOuterLogicalHeight
= enclosingFlowThread
->pageRemainingLogicalHeightForOffset(blockOffsetInEnclosingFlowThread(), LayoutBlock::AssociateWithLatterPage
);
362 ASSERT(remainingOuterLogicalHeight
> 0);
363 if (maxHeight
> remainingOuterLogicalHeight
)
364 maxHeight
= remainingOuterLogicalHeight
;
370 void MultiColumnFragmentainerGroup::setAndConstrainColumnHeight(LayoutUnit newHeight
)
372 m_columnHeight
= newHeight
;
373 if (m_columnHeight
> m_maxColumnHeight
)
374 m_columnHeight
= m_maxColumnHeight
;
377 unsigned MultiColumnFragmentainerGroup::findRunWithTallestColumns() const
379 unsigned indexWithLargestHeight
= 0;
380 LayoutUnit largestHeight
;
381 LayoutUnit previousOffset
= m_logicalTopInFlowThread
;
382 size_t runCount
= m_contentRuns
.size();
384 for (size_t i
= 0; i
< runCount
; i
++) {
385 const ContentRun
& run
= m_contentRuns
[i
];
386 LayoutUnit height
= run
.columnLogicalHeight(previousOffset
);
387 if (largestHeight
< height
) {
388 largestHeight
= height
;
389 indexWithLargestHeight
= i
;
391 previousOffset
= run
.breakOffset();
393 return indexWithLargestHeight
;
396 void MultiColumnFragmentainerGroup::distributeImplicitBreaks()
399 // There should be no implicit breaks assumed at this point.
400 for (unsigned i
= 0; i
< m_contentRuns
.size(); i
++)
401 ASSERT(!m_contentRuns
[i
].assumedImplicitBreaks());
402 #endif // ENABLE(ASSERT)
404 // Insert a final content run to encompass all content. This will include overflow if this is
406 addContentRun(m_logicalBottomInFlowThread
);
407 unsigned columnCount
= m_contentRuns
.size();
409 // If there is room for more breaks (to reach the used value of column-count), imagine that we
410 // insert implicit breaks at suitable locations. At any given time, the content run with the
411 // currently tallest columns will get another implicit break "inserted", which will increase its
412 // column count by one and shrink its columns' height. Repeat until we have the desired total
413 // number of breaks. The largest column height among the runs will then be the initial column
414 // height for the balancer to use.
415 while (columnCount
< m_columnSet
.usedColumnCount()) {
416 unsigned index
= findRunWithTallestColumns();
417 m_contentRuns
[index
].assumeAnotherImplicitBreak();
422 LayoutUnit
MultiColumnFragmentainerGroup::calculateColumnHeight(BalancedColumnHeightCalculation calculationMode
) const
424 if (calculationMode
== GuessFromFlowThreadPortion
) {
425 // Initial balancing. Start with the lowest imaginable column height. We use the tallest
426 // content run (after having "inserted" implicit breaks), and find its start offset (by
427 // looking at the previous run's end offset, or, if there's no previous run, the set's start
428 // offset in the flow thread).
429 unsigned index
= findRunWithTallestColumns();
430 LayoutUnit startOffset
= index
> 0 ? m_contentRuns
[index
- 1].breakOffset() : m_logicalTopInFlowThread
;
431 return std::max
<LayoutUnit
>(m_contentRuns
[index
].columnLogicalHeight(startOffset
), m_minimumColumnHeight
);
434 if (actualColumnCount() <= m_columnSet
.usedColumnCount()) {
435 // With the current column height, the content fits without creating overflowing columns. We're done.
436 return m_columnHeight
;
439 if (m_contentRuns
.size() >= m_columnSet
.usedColumnCount()) {
440 // Too many forced breaks to allow any implicit breaks. Initial balancing should already
441 // have set a good height. There's nothing more we should do.
442 return m_columnHeight
;
445 if (m_columnHeight
>= m_maxColumnHeight
) {
446 // We cannot stretch any further. We'll just have to live with the overflowing columns. This
447 // typically happens if the max column height is less than the height of the tallest piece
448 // of unbreakable content (e.g. lines).
449 return m_columnHeight
;
452 // If the initial guessed column height wasn't enough, stretch it now. Stretch by the lowest
453 // amount of space shortage found during layout.
455 ASSERT(m_minSpaceShortage
> 0); // We should never _shrink_ the height!
456 ASSERT(m_minSpaceShortage
!= LayoutUnit::max()); // If this happens, we probably have a bug.
457 if (m_minSpaceShortage
== LayoutUnit::max())
458 return m_columnHeight
; // So bail out rather than looping infinitely.
460 return m_columnHeight
+ m_minSpaceShortage
;
463 LayoutRect
MultiColumnFragmentainerGroup::columnRectAt(unsigned columnIndex
) const
465 LayoutUnit columnLogicalWidth
= m_columnSet
.pageLogicalWidth();
466 LayoutUnit columnLogicalHeight
= m_columnHeight
;
467 LayoutUnit columnLogicalTop
;
468 LayoutUnit columnLogicalLeft
;
469 LayoutUnit columnGap
= m_columnSet
.columnGap();
470 LayoutUnit portionOutsideFlowThread
= logicalTopInFlowThread() + (columnIndex
+ 1) * columnLogicalHeight
- logicalBottomInFlowThread();
471 if (portionOutsideFlowThread
> 0) {
472 // The last column may not be using all available space.
473 ASSERT(columnIndex
+ 1 == actualColumnCount());
474 columnLogicalHeight
-= portionOutsideFlowThread
;
475 ASSERT(columnLogicalHeight
>= 0);
478 if (m_columnSet
.multiColumnFlowThread()->progressionIsInline()) {
479 if (m_columnSet
.style()->isLeftToRightDirection())
480 columnLogicalLeft
+= columnIndex
* (columnLogicalWidth
+ columnGap
);
482 columnLogicalLeft
+= m_columnSet
.contentLogicalWidth() - columnLogicalWidth
- columnIndex
* (columnLogicalWidth
+ columnGap
);
484 columnLogicalTop
+= columnIndex
* (m_columnHeight
+ columnGap
);
487 LayoutRect
columnRect(columnLogicalLeft
, columnLogicalTop
, columnLogicalWidth
, columnLogicalHeight
);
488 if (!m_columnSet
.isHorizontalWritingMode())
489 return columnRect
.transposedRect();
493 LayoutRect
MultiColumnFragmentainerGroup::flowThreadPortionRectAt(unsigned columnIndex
) const
495 LayoutUnit logicalTop
= logicalTopInFlowThreadAt(columnIndex
);
496 LayoutUnit logicalBottom
= logicalTop
+ m_columnHeight
;
497 if (logicalBottom
> logicalBottomInFlowThread()) {
498 // The last column may not be using all available space.
499 ASSERT(columnIndex
+ 1 == actualColumnCount());
500 logicalBottom
= logicalBottomInFlowThread();
501 ASSERT(logicalBottom
>= logicalTop
);
503 LayoutUnit portionLogicalHeight
= logicalBottom
- logicalTop
;
504 if (m_columnSet
.isHorizontalWritingMode())
505 return LayoutRect(LayoutUnit(), logicalTop
, m_columnSet
.pageLogicalWidth(), portionLogicalHeight
);
506 return LayoutRect(logicalTop
, LayoutUnit(), portionLogicalHeight
, m_columnSet
.pageLogicalWidth());
509 LayoutRect
MultiColumnFragmentainerGroup::flowThreadPortionOverflowRectAt(unsigned columnIndex
) const
511 // This function determines the portion of the flow thread that paints for the column. Along the inline axis, columns are
512 // unclipped at outside edges (i.e., the first and last column in the set), and they clip to half the column
513 // gap along interior edges.
515 // In the block direction, we will not clip overflow out of the top of the first column, or out of the bottom of
516 // the last column. This applies only to the true first column and last column across all column sets.
518 // FIXME: Eventually we will know overflow on a per-column basis, but we can't do this until we have a painting
519 // mode that understands not to paint contents from a previous column in the overflow area of a following column.
520 bool isFirstColumnInRow
= !columnIndex
;
521 bool isLastColumnInRow
= columnIndex
== actualColumnCount() - 1;
522 bool isLTR
= m_columnSet
.style()->isLeftToRightDirection();
523 bool isLeftmostColumn
= isLTR
? isFirstColumnInRow
: isLastColumnInRow
;
524 bool isRightmostColumn
= isLTR
? isLastColumnInRow
: isFirstColumnInRow
;
526 LayoutRect portionRect
= flowThreadPortionRectAt(columnIndex
);
527 bool isFirstColumnInMulticolContainer
= isFirstColumnInRow
&& this == &m_columnSet
.firstFragmentainerGroup() && !m_columnSet
.previousSiblingMultiColumnSet();
528 bool isLastColumnInMulticolContainer
= isLastColumnInRow
&& this == &m_columnSet
.lastFragmentainerGroup() && !m_columnSet
.nextSiblingMultiColumnSet();
529 // Calculate the overflow rectangle, based on the flow thread's, clipped at column logical
530 // top/bottom unless it's the first/last column.
531 LayoutRect overflowRect
= m_columnSet
.overflowRectForFlowThreadPortion(portionRect
, isFirstColumnInMulticolContainer
, isLastColumnInMulticolContainer
);
533 // Avoid overflowing into neighboring columns, by clipping in the middle of adjacent column
534 // gaps. Also make sure that we avoid rounding errors.
535 LayoutUnit columnGap
= m_columnSet
.columnGap();
536 if (m_columnSet
.isHorizontalWritingMode()) {
537 if (!isLeftmostColumn
)
538 overflowRect
.shiftXEdgeTo(portionRect
.x() - columnGap
/ 2);
539 if (!isRightmostColumn
)
540 overflowRect
.shiftMaxXEdgeTo(portionRect
.maxX() + columnGap
- columnGap
/ 2);
542 if (!isLeftmostColumn
)
543 overflowRect
.shiftYEdgeTo(portionRect
.y() - columnGap
/ 2);
544 if (!isRightmostColumn
)
545 overflowRect
.shiftMaxYEdgeTo(portionRect
.maxY() + columnGap
- columnGap
/ 2);
550 unsigned MultiColumnFragmentainerGroup::columnIndexAtOffset(LayoutUnit offsetInFlowThread
, ColumnIndexCalculationMode mode
) const
552 // Handle the offset being out of range.
553 if (offsetInFlowThread
< m_logicalTopInFlowThread
)
555 // If we're laying out right now, we cannot constrain against some logical bottom, since it
556 // isn't known yet. Otherwise, just return the last column if we're past the logical bottom.
557 if (mode
== ClampToExistingColumns
) {
558 if (offsetInFlowThread
>= m_logicalBottomInFlowThread
)
559 return actualColumnCount() - 1;
563 return ((offsetInFlowThread
- m_logicalTopInFlowThread
) / m_columnHeight
).floor();
567 unsigned MultiColumnFragmentainerGroup::columnIndexAtVisualPoint(const LayoutPoint
& visualPoint
) const
569 bool isColumnProgressionInline
= m_columnSet
.multiColumnFlowThread()->progressionIsInline();
570 bool isHorizontalWritingMode
= m_columnSet
.isHorizontalWritingMode();
571 LayoutUnit columnLengthInColumnProgressionDirection
= isColumnProgressionInline
? m_columnSet
.pageLogicalWidth() : logicalHeight();
572 LayoutUnit offsetInColumnProgressionDirection
= isHorizontalWritingMode
== isColumnProgressionInline
? visualPoint
.x() : visualPoint
.y();
573 if (!m_columnSet
.style()->isLeftToRightDirection() && isColumnProgressionInline
)
574 offsetInColumnProgressionDirection
= m_columnSet
.logicalWidth() - offsetInColumnProgressionDirection
;
575 LayoutUnit columnGap
= m_columnSet
.columnGap();
576 if (columnLengthInColumnProgressionDirection
+ columnGap
<= 0)
578 // Column boundaries are in the middle of the column gap.
579 int index
= (offsetInColumnProgressionDirection
+ columnGap
/ 2) / (columnLengthInColumnProgressionDirection
+ columnGap
);
582 return std::min(unsigned(index
), actualColumnCount() - 1);
585 void MultiColumnFragmentainerGroup::columnIntervalForBlockRangeInFlowThread(LayoutUnit logicalTopInFlowThread
, LayoutUnit logicalBottomInFlowThread
, unsigned& firstColumn
, unsigned& lastColumn
) const
587 ASSERT(logicalTopInFlowThread
<= logicalBottomInFlowThread
);
588 firstColumn
= columnIndexAtOffset(logicalTopInFlowThread
);
589 lastColumn
= columnIndexAtOffset(logicalBottomInFlowThread
);
590 // logicalBottomInFlowThread is an exclusive endpoint, so some additional adjustments may be necessary.
591 if (lastColumn
> firstColumn
&& logicalTopInFlowThreadAt(lastColumn
) == logicalBottomInFlowThread
)
595 void MultiColumnFragmentainerGroup::columnIntervalForVisualRect(const LayoutRect
& rect
, unsigned& firstColumn
, unsigned& lastColumn
) const
597 bool isColumnProgressionInline
= m_columnSet
.multiColumnFlowThread()->progressionIsInline();
598 bool isFlippedColumnProgression
= !m_columnSet
.style()->isLeftToRightDirection() && isColumnProgressionInline
;
599 if (m_columnSet
.isHorizontalWritingMode() == isColumnProgressionInline
) {
600 if (isFlippedColumnProgression
) {
601 firstColumn
= columnIndexAtVisualPoint(rect
.maxXMinYCorner());
602 lastColumn
= columnIndexAtVisualPoint(rect
.minXMinYCorner());
604 firstColumn
= columnIndexAtVisualPoint(rect
.minXMinYCorner());
605 lastColumn
= columnIndexAtVisualPoint(rect
.maxXMinYCorner());
608 if (isFlippedColumnProgression
) {
609 firstColumn
= columnIndexAtVisualPoint(rect
.minXMaxYCorner());
610 lastColumn
= columnIndexAtVisualPoint(rect
.minXMinYCorner());
612 firstColumn
= columnIndexAtVisualPoint(rect
.minXMinYCorner());
613 lastColumn
= columnIndexAtVisualPoint(rect
.minXMaxYCorner());
616 ASSERT(firstColumn
<= lastColumn
);
619 MultiColumnFragmentainerGroupList::MultiColumnFragmentainerGroupList(LayoutMultiColumnSet
& columnSet
)
620 : m_columnSet(columnSet
)
622 append(MultiColumnFragmentainerGroup(m_columnSet
));
625 // An explicit empty destructor of MultiColumnFragmentainerGroupList should be in
626 // MultiColumnFragmentainerGroup.cpp, because if an implicit destructor is used,
627 // msvc 2015 tries to generate its destructor (because the class is dll-exported class)
628 // and causes a compile error because of lack of MultiColumnFragmentainerGroup::operator=.
629 // Since MultiColumnFragmentainerGroup is non-copyable, we cannot define the operator=.
630 MultiColumnFragmentainerGroupList::~MultiColumnFragmentainerGroupList()
634 MultiColumnFragmentainerGroup
& MultiColumnFragmentainerGroupList::addExtraGroup()
636 append(MultiColumnFragmentainerGroup(m_columnSet
));
640 void MultiColumnFragmentainerGroupList::deleteExtraGroups()