OLD | NEW |
---|---|
(Empty) | |
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. | |
4 | |
5 #include "config.h" | |
6 | |
7 #include "core/layout/MultiColumnFragmentainerGroup.h" | |
8 | |
9 #include "core/rendering/RenderMultiColumnSet.h" | |
10 | |
11 namespace blink { | |
12 | |
13 MultiColumnFragmentainerGroup::MultiColumnFragmentainerGroup(RenderMultiColumnSe t& columnSet) | |
14 : m_columnSet(columnSet) | |
15 { | |
16 } | |
17 | |
18 bool MultiColumnFragmentainerGroup::isLastGroup() const | |
19 { | |
20 return &m_columnSet.lastFragmentainerGroup() == this; | |
21 } | |
22 | |
23 LayoutSize MultiColumnFragmentainerGroup::offsetFromColumnSet() const | |
24 { | |
25 LayoutSize offset(LayoutUnit(), logicalTop()); | |
26 if (!m_columnSet.flowThread()->isHorizontalWritingMode()) | |
27 return offset.transposedSize(); | |
28 return offset; | |
29 } | |
30 | |
31 bool MultiColumnFragmentainerGroup::heightIsAuto() const | |
32 { | |
33 // Only the last row may have auto height, and thus be balanced. There are n o good reasons to | |
34 // balance the preceding rows, and that could potentially lead to an insane number of layout | |
35 // passes as well. | |
Julien - ping for review
2015/02/11 07:25:29
Maybe there is some ASSERT we could land to avoid
mstensho (USE GERRIT)
2015/02/11 09:49:54
This very code already makes sure that we only bal
| |
36 return isLastGroup() && m_columnSet.heightIsAuto(); | |
37 } | |
38 | |
39 void MultiColumnFragmentainerGroup::resetColumnHeight() | |
40 { | |
41 // Nuke previously stored minimum column height. Contents may have changed f or all we know. | |
42 m_minimumColumnHeight = 0; | |
43 | |
44 m_maxColumnHeight = calculateMaxColumnHeight(); | |
45 | |
46 LayoutUnit oldColumnHeight = m_columnHeight; | |
47 | |
48 if (heightIsAuto()) | |
49 m_columnHeight = LayoutUnit(); | |
50 else | |
51 setAndConstrainColumnHeight(heightAdjustedForRowOffset(m_columnSet.multi ColumnFlowThread()->columnHeightAvailable())); | |
52 | |
53 if (m_columnHeight != oldColumnHeight) | |
54 m_columnSet.setChildNeedsLayout(MarkOnlyThis); | |
55 | |
56 // Content runs are only needed in the initial layout pass, in order to find an initial column | |
57 // height, and should have been deleted afterwards. We're about to rebuild t he content runs, so | |
58 // the list needs to be empty. | |
59 ASSERT(m_contentRuns.isEmpty()); | |
60 } | |
61 | |
62 void MultiColumnFragmentainerGroup::addContentRun(LayoutUnit endOffsetInFlowThre ad) | |
63 { | |
64 if (!m_contentRuns.isEmpty() && endOffsetInFlowThread <= m_contentRuns.last( ).breakOffset()) | |
65 return; | |
66 // Append another item as long as we haven't exceeded used column count. Wha t ends up in the | |
67 // overflow area shouldn't affect column balancing. | |
68 if (m_contentRuns.size() < m_columnSet.usedColumnCount()) | |
69 m_contentRuns.append(ContentRun(endOffsetInFlowThread)); | |
70 } | |
71 | |
72 void MultiColumnFragmentainerGroup::recordSpaceShortage(LayoutUnit spaceShortage ) | |
73 { | |
74 if (spaceShortage >= m_minSpaceShortage) | |
75 return; | |
76 | |
77 // The space shortage is what we use as our stretch amount. We need a positi ve number here in | |
78 // order to get anywhere. | |
79 ASSERT(spaceShortage > 0); | |
80 | |
81 m_minSpaceShortage = spaceShortage; | |
82 } | |
83 | |
84 bool MultiColumnFragmentainerGroup::recalculateColumnHeight(BalancedColumnHeight Calculation calculationMode) | |
85 { | |
86 LayoutUnit oldColumnHeight = m_columnHeight; | |
87 | |
88 m_maxColumnHeight = calculateMaxColumnHeight(); | |
89 | |
90 if (heightIsAuto()) { | |
91 if (calculationMode == GuessFromFlowThreadPortion) { | |
92 // Post-process the content runs and find out where the implicit bre aks will occur. | |
93 distributeImplicitBreaks(); | |
94 } | |
95 LayoutUnit newColumnHeight = calculateColumnHeight(calculationMode); | |
96 setAndConstrainColumnHeight(newColumnHeight); | |
97 // After having calculated an initial column height, the multicol contai ner typically needs at | |
98 // least one more layout pass with a new column height, but if a height was specified, we only | |
99 // need to do this if we think that we need less space than specified. C onversely, if we | |
100 // determined that the columns need to be as tall as the specified heigh t of the container, we | |
101 // have already laid it out correctly, and there's no need for another p ass. | |
102 } else { | |
103 // The position of the column set may have changed, in which case height available for | |
104 // columns may have changed as well. | |
105 setAndConstrainColumnHeight(m_columnHeight); | |
106 } | |
107 | |
108 // We can get rid of the content runs now, if we haven't already done so. Th ey are only needed | |
109 // to calculate the initial balanced column height. In fact, we have to get rid of them before | |
110 // the next layout pass, since each pass will rebuild this. | |
111 m_contentRuns.clear(); | |
112 | |
113 if (m_columnHeight == oldColumnHeight) | |
114 return false; // No change. We're done. | |
115 | |
116 m_minSpaceShortage = RenderFlowThread::maxLogicalHeight(); | |
117 return true; // Need another pass. | |
118 } | |
119 | |
120 void MultiColumnFragmentainerGroup::expandToEncompassFlowThreadOverflow() | |
121 { | |
122 ASSERT(isLastGroup()); | |
123 // Get the offset within the flow thread in its block progression direction. Then get the | |
124 // flow thread's remaining logical height including its overflow and expand our rect | |
125 // to encompass that remaining height and overflow. The idea is that we will generate | |
126 // additional columns and pages to hold that overflow, since people do write bad | |
127 // content like <body style="height:0px"> in multi-column layouts. | |
128 RenderMultiColumnFlowThread* flowThread = m_columnSet.multiColumnFlowThread( ); | |
129 LayoutRect layoutRect = flowThread->layoutOverflowRect(); | |
130 m_logicalBottomInFlowThread = flowThread->isHorizontalWritingMode() ? layout Rect.maxY() : layoutRect.maxX(); | |
131 } | |
132 | |
133 LayoutSize MultiColumnFragmentainerGroup::flowThreadTranslationAtOffset(LayoutUn it offsetInFlowThread) const | |
134 { | |
135 unsigned columnIndex = columnIndexAtOffset(offsetInFlowThread); | |
136 LayoutRect portionRect(flowThreadPortionRectAt(columnIndex)); | |
137 m_columnSet.flipForWritingMode(portionRect); | |
138 LayoutRect columnRect(columnRectAt(columnIndex)); | |
139 m_columnSet.flipForWritingMode(columnRect); | |
140 return columnRect.location() - portionRect.location(); | |
141 } | |
142 | |
143 LayoutUnit MultiColumnFragmentainerGroup::columnLogicalTopForOffset(LayoutUnit o ffsetInFlowThread) const | |
144 { | |
145 unsigned columnIndex = columnIndexAtOffset(offsetInFlowThread, AssumeNewColu mns); | |
146 return m_logicalTopInFlowThread + columnIndex * m_columnHeight; | |
147 } | |
148 | |
149 void MultiColumnFragmentainerGroup::collectLayerFragments(LayerFragments& fragme nts, const LayoutRect& layerBoundingBox, const LayoutRect& dirtyRect) const | |
150 { | |
151 // |layerBoundingBox| is in the flow thread coordinate space, relative to th e top/left edge of | |
152 // the flow thread, but note that it has been converted with respect to writ ing mode (so that | |
153 // it's visual/physical in that sense). | |
154 // | |
155 // |dirtyRect| is visual, relative to the multicol container. | |
156 // | |
157 // Then there's the output from this method - the stuff we put into the list of fragments. The | |
158 // fragment.paginationOffset point is the actual visual translation required to get from a | |
159 // location in the flow thread to a location in a given column. The fragment .paginationClip | |
160 // rectangle, on the other hand, is in flow thread coordinates. | |
161 // | |
162 // All other rectangles in this method are sized physically, and the inline direction coordinate | |
163 // is physical too, but the block direction coordinate is "logical top". Thi s is the same as | |
164 // e.g. RenderBox::frameRect(). These rectangles also pretend that there's o nly one long column, | |
165 // i.e. they are for the flow thread. | |
166 | |
167 RenderMultiColumnFlowThread* flowThread = m_columnSet.multiColumnFlowThread( ); | |
168 bool isHorizontalWritingMode = m_columnSet.isHorizontalWritingMode(); | |
169 | |
170 // Put the layer bounds into flow thread-local coordinates by flipping it fi rst. Since we're in | |
171 // a renderer, most rectangles are represented this way. | |
172 LayoutRect layerBoundsInFlowThread(layerBoundingBox); | |
173 flowThread->flipForWritingMode(layerBoundsInFlowThread); | |
174 | |
175 // Now we can compare with the flow thread portions owned by each column. Fi rst let's | |
176 // see if the rect intersects our flow thread portion at all. | |
177 LayoutRect clippedRect(layerBoundsInFlowThread); | |
178 clippedRect.intersect(m_columnSet.RenderRegion::flowThreadPortionOverflowRec t()); // FIXME: clean up this mess. | |
179 if (clippedRect.isEmpty()) | |
180 return; | |
181 | |
182 // Now we know we intersect at least one column. Let's figure out the logica l top and logical | |
183 // bottom of the area we're checking. | |
184 LayoutUnit layerLogicalTop = isHorizontalWritingMode ? layerBoundsInFlowThre ad.y() : layerBoundsInFlowThread.x(); | |
185 LayoutUnit layerLogicalBottom = (isHorizontalWritingMode ? layerBoundsInFlow Thread.maxY() : layerBoundsInFlowThread.maxX()) - 1; | |
186 | |
187 // Figure out the start and end columns and only check within that range so that we don't walk the | |
188 // entire column row. | |
189 unsigned startColumn = columnIndexAtOffset(layerLogicalTop); | |
190 unsigned endColumn = columnIndexAtOffset(layerLogicalBottom); | |
191 | |
192 LayoutUnit colLogicalWidth = m_columnSet.pageLogicalWidth(); | |
193 LayoutUnit colGap = m_columnSet.columnGap(); | |
194 unsigned colCount = actualColumnCount(); | |
195 | |
196 bool progressionIsInline = flowThread->progressionIsInline(); | |
197 bool leftToRight = m_columnSet.style()->isLeftToRightDirection(); | |
198 | |
199 LayoutUnit initialBlockOffset = m_columnSet.logicalTop() + logicalTop() - fl owThread->logicalTop(); | |
200 | |
201 for (unsigned i = startColumn; i <= endColumn; i++) { | |
202 // Get the portion of the flow thread that corresponds to this column. | |
203 LayoutRect flowThreadPortion = flowThreadPortionRectAt(i); | |
204 | |
205 // Now get the overflow rect that corresponds to the column. | |
206 LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flo wThreadPortion, i, colCount, colGap); | |
207 | |
208 // In order to create a fragment we must intersect the portion painted b y this column. | |
209 LayoutRect clippedRect(layerBoundsInFlowThread); | |
210 clippedRect.intersect(flowThreadOverflowPortion); | |
211 if (clippedRect.isEmpty()) | |
212 continue; | |
213 | |
214 // We also need to intersect the dirty rect. We have to apply a translat ion and shift based off | |
215 // our column index. | |
216 LayoutPoint translationOffset; | |
217 LayoutUnit inlineOffset = progressionIsInline ? i * (colLogicalWidth + c olGap) : LayoutUnit(); | |
218 if (!leftToRight) | |
219 inlineOffset = -inlineOffset; | |
220 translationOffset.setX(inlineOffset); | |
221 LayoutUnit blockOffset; | |
222 if (progressionIsInline) { | |
223 blockOffset = initialBlockOffset + (isHorizontalWritingMode ? -flowT hreadPortion.y() : -flowThreadPortion.x()); | |
224 } else { | |
225 // Column gap can apply in the block direction for page fragmentaine rs. | |
226 // There is currently no spec which calls for column-gap to apply | |
227 // for page fragmentainers at all, but it's applied here for compati bility | |
228 // with the old multicolumn implementation. | |
229 blockOffset = i * colGap; | |
230 } | |
231 if (isFlippedBlocksWritingMode(m_columnSet.style()->writingMode())) | |
232 blockOffset = -blockOffset; | |
233 translationOffset.setY(blockOffset); | |
234 if (!isHorizontalWritingMode) | |
235 translationOffset = translationOffset.transposedPoint(); | |
236 | |
237 // Shift the dirty rect to be in flow thread coordinates with this trans lation applied. | |
238 LayoutRect translatedDirtyRect(dirtyRect); | |
239 translatedDirtyRect.moveBy(-translationOffset); | |
240 | |
241 // See if we intersect the dirty rect. | |
242 clippedRect = layerBoundingBox; | |
243 clippedRect.intersect(translatedDirtyRect); | |
244 if (clippedRect.isEmpty()) | |
245 continue; | |
246 | |
247 // Something does need to paint in this column. Make a fragment now and supply the physical translation | |
248 // offset and the clip rect for the column with that offset applied. | |
249 LayerFragment fragment; | |
250 fragment.paginationOffset = translationOffset; | |
251 | |
252 LayoutRect flippedFlowThreadOverflowPortion(flowThreadOverflowPortion); | |
253 // Flip it into more a physical (RenderLayer-style) rectangle. | |
254 flowThread->flipForWritingMode(flippedFlowThreadOverflowPortion); | |
255 fragment.paginationClip = flippedFlowThreadOverflowPortion; | |
256 fragments.append(fragment); | |
257 } | |
258 } | |
259 | |
260 LayoutRect MultiColumnFragmentainerGroup::calculateOverflow() const | |
261 { | |
262 unsigned columnCount = actualColumnCount(); | |
263 if (!columnCount) | |
264 return LayoutRect(); | |
265 return columnRectAt(columnCount - 1); | |
266 } | |
267 | |
268 unsigned MultiColumnFragmentainerGroup::actualColumnCount() const | |
269 { | |
270 // We must always return a value of 1 or greater. Column count = 0 is a mean ingless situation, | |
271 // and will confuse and cause problems in other parts of the code. | |
272 if (!m_columnHeight) | |
273 return 1; | |
274 | |
275 // Our flow thread portion determines our column count. We have as many colu mns as needed to fit | |
276 // all the content. | |
277 LayoutUnit flowThreadPortionHeight = logicalHeightInFlowThread(); | |
278 if (!flowThreadPortionHeight) | |
279 return 1; | |
280 | |
281 unsigned count = ceil(flowThreadPortionHeight.toFloat() / m_columnHeight.toF loat()); | |
282 ASSERT(count >= 1); | |
283 return count; | |
284 } | |
285 | |
286 LayoutUnit MultiColumnFragmentainerGroup::heightAdjustedForRowOffset(LayoutUnit height) const | |
287 { | |
288 // Adjust for the top offset within the content box of the multicol containe r (containing | |
289 // block), unless we're in the first set. We know that the top offset for th e first set will be | |
290 // zero, but if the multicol container has non-zero top border or padding, t he set's top offset | |
291 // (initially being 0 and relative to the border box) will be negative until it has been laid | |
292 // out. Had we used this bogus offset, we would calculate the wrong height, and risk performing | |
293 // a wasted layout iteration. Of course all other sets (if any) have this pr oblem in the first | |
294 // layout pass too, but there's really nothing we can do there until the flo w thread has been | |
295 // laid out anyway. | |
296 if (m_columnSet.previousSiblingMultiColumnSet()) { | |
297 RenderBlockFlow* multicolBlock = m_columnSet.multiColumnBlockFlow(); | |
298 LayoutUnit contentLogicalTop = m_columnSet.logicalTop() - multicolBlock- >borderAndPaddingBefore(); | |
299 height -= contentLogicalTop; | |
300 } | |
301 height -= logicalTop(); | |
302 return max(height, LayoutUnit(1)); // Let's avoid zero height, as that would probably cause an infinite amount of columns to be created. | |
303 } | |
304 | |
305 LayoutUnit MultiColumnFragmentainerGroup::calculateMaxColumnHeight() const | |
306 { | |
307 RenderBlockFlow* multicolBlock = m_columnSet.multiColumnBlockFlow(); | |
308 LayoutStyle* multicolStyle = multicolBlock->style(); | |
309 LayoutUnit availableHeight = m_columnSet.multiColumnFlowThread()->columnHeig htAvailable(); | |
310 LayoutUnit maxColumnHeight = availableHeight ? availableHeight : RenderFlowT hread::maxLogicalHeight(); | |
311 if (!multicolStyle->logicalMaxHeight().isMaxSizeNone()) { | |
312 LayoutUnit logicalMaxHeight = multicolBlock->computeContentLogicalHeight (multicolStyle->logicalMaxHeight(), -1); | |
313 if (logicalMaxHeight != -1 && maxColumnHeight > logicalMaxHeight) | |
314 maxColumnHeight = logicalMaxHeight; | |
315 } | |
316 return heightAdjustedForRowOffset(maxColumnHeight); | |
317 } | |
318 | |
319 void MultiColumnFragmentainerGroup::setAndConstrainColumnHeight(LayoutUnit newHe ight) | |
320 { | |
321 m_columnHeight = newHeight; | |
322 if (m_columnHeight > m_maxColumnHeight) | |
323 m_columnHeight = m_maxColumnHeight; | |
324 // FIXME: the height may also be affected by the enclosing pagination contex t, if any. | |
325 } | |
326 | |
327 unsigned MultiColumnFragmentainerGroup::findRunWithTallestColumns() const | |
328 { | |
329 unsigned indexWithLargestHeight = 0; | |
330 LayoutUnit largestHeight; | |
331 LayoutUnit previousOffset = m_logicalTopInFlowThread; | |
332 size_t runCount = m_contentRuns.size(); | |
333 ASSERT(runCount); | |
334 for (size_t i = 0; i < runCount; i++) { | |
335 const ContentRun& run = m_contentRuns[i]; | |
336 LayoutUnit height = run.columnLogicalHeight(previousOffset); | |
337 if (largestHeight < height) { | |
338 largestHeight = height; | |
339 indexWithLargestHeight = i; | |
340 } | |
341 previousOffset = run.breakOffset(); | |
342 } | |
343 return indexWithLargestHeight; | |
344 } | |
345 | |
346 void MultiColumnFragmentainerGroup::distributeImplicitBreaks() | |
347 { | |
348 #if ENABLE(ASSERT) | |
349 // There should be no implicit breaks assumed at this point. | |
350 for (unsigned i = 0; i < m_contentRuns.size(); i++) | |
351 ASSERT(!m_contentRuns[i].assumedImplicitBreaks()); | |
352 #endif // ENABLE(ASSERT) | |
353 | |
354 // Insert a final content run to encompass all content. This will include ov erflow if this is | |
355 // the last set. | |
356 addContentRun(m_logicalBottomInFlowThread); | |
357 unsigned columnCount = m_contentRuns.size(); | |
358 | |
359 // If there is room for more breaks (to reach the used value of column-count ), imagine that we | |
360 // insert implicit breaks at suitable locations. At any given time, the cont ent run with the | |
361 // currently tallest columns will get another implicit break "inserted", whi ch will increase its | |
362 // column count by one and shrink its columns' height. Repeat until we have the desired total | |
363 // number of breaks. The largest column height among the runs will then be t he initial column | |
364 // height for the balancer to use. | |
365 while (columnCount < m_columnSet.usedColumnCount()) { | |
366 unsigned index = findRunWithTallestColumns(); | |
367 m_contentRuns[index].assumeAnotherImplicitBreak(); | |
368 columnCount++; | |
369 } | |
370 } | |
371 | |
372 LayoutUnit MultiColumnFragmentainerGroup::calculateColumnHeight(BalancedColumnHe ightCalculation calculationMode) const | |
373 { | |
374 if (calculationMode == GuessFromFlowThreadPortion) { | |
375 // Initial balancing. Start with the lowest imaginable column height. We use the tallest | |
376 // content run (after having "inserted" implicit breaks), and find its s tart offset (by | |
377 // looking at the previous run's end offset, or, if there's no previous run, the set's start | |
378 // offset in the flow thread). | |
379 unsigned index = findRunWithTallestColumns(); | |
380 LayoutUnit startOffset = index > 0 ? m_contentRuns[index - 1].breakOffse t() : m_logicalTopInFlowThread; | |
381 return std::max<LayoutUnit>(m_contentRuns[index].columnLogicalHeight(sta rtOffset), m_minimumColumnHeight); | |
382 } | |
383 | |
384 if (actualColumnCount() <= m_columnSet.usedColumnCount()) { | |
385 // With the current column height, the content fits without creating ove rflowing columns. We're done. | |
386 return m_columnHeight; | |
387 } | |
388 | |
389 if (m_contentRuns.size() >= m_columnSet.usedColumnCount()) { | |
390 // Too many forced breaks to allow any implicit breaks. Initial balancin g should already | |
391 // have set a good height. There's nothing more we should do. | |
392 return m_columnHeight; | |
393 } | |
394 | |
395 // If the initial guessed column height wasn't enough, stretch it now. Stret ch by the lowest | |
396 // amount of space shortage found during layout. | |
397 | |
398 ASSERT(m_minSpaceShortage > 0); // We should never _shrink_ the height! | |
399 ASSERT(m_minSpaceShortage != RenderFlowThread::maxLogicalHeight()); // If th is happens, we probably have a bug. | |
400 if (m_minSpaceShortage == RenderFlowThread::maxLogicalHeight()) | |
401 return m_columnHeight; // So bail out rather than looping infinitely. | |
402 | |
403 return m_columnHeight + m_minSpaceShortage; | |
404 } | |
405 | |
406 LayoutRect MultiColumnFragmentainerGroup::columnRectAt(unsigned columnIndex) con st | |
407 { | |
408 LayoutUnit columnLogicalWidth = m_columnSet.pageLogicalWidth(); | |
409 LayoutUnit columnLogicalHeight = m_columnHeight; | |
410 LayoutUnit columnLogicalTop; | |
411 LayoutUnit columnLogicalLeft; | |
412 LayoutUnit columnGap = m_columnSet.columnGap(); | |
413 | |
414 if (m_columnSet.multiColumnFlowThread()->progressionIsInline()) { | |
415 if (m_columnSet.style()->isLeftToRightDirection()) | |
416 columnLogicalLeft += columnIndex * (columnLogicalWidth + columnGap); | |
417 else | |
418 columnLogicalLeft += m_columnSet.contentLogicalWidth() - columnLogic alWidth - columnIndex * (columnLogicalWidth + columnGap); | |
419 } else { | |
420 columnLogicalTop += columnIndex * (columnLogicalHeight + columnGap); | |
421 } | |
422 | |
423 LayoutRect columnRect(columnLogicalLeft, columnLogicalTop, columnLogicalWidt h, columnLogicalHeight); | |
424 if (!m_columnSet.isHorizontalWritingMode()) | |
425 return columnRect.transposedRect(); | |
426 return columnRect; | |
427 } | |
428 | |
429 LayoutRect MultiColumnFragmentainerGroup::flowThreadPortionRectAt(unsigned colum nIndex) const | |
430 { | |
431 LayoutUnit logicalTop = m_logicalTopInFlowThread + columnIndex * m_columnHei ght; | |
432 if (m_columnSet.isHorizontalWritingMode()) | |
433 return LayoutRect(LayoutUnit(), logicalTop, m_columnSet.pageLogicalWidth (), m_columnHeight); | |
434 return LayoutRect(logicalTop, LayoutUnit(), m_columnHeight, m_columnSet.page LogicalWidth()); | |
435 } | |
436 | |
437 LayoutRect MultiColumnFragmentainerGroup::flowThreadPortionOverflowRect(const La youtRect& portionRect, unsigned columnIndex, unsigned columnCount, LayoutUnit co lumnGap) const | |
438 { | |
439 // This function determines the portion of the flow thread that paints for t he column. Along the inline axis, columns are | |
440 // unclipped at outside edges (i.e., the first and last column in the set), and they clip to half the column | |
441 // gap along interior edges. | |
442 // | |
443 // In the block direction, we will not clip overflow out of the top of the f irst column, or out of the bottom of | |
444 // the last column. This applies only to the true first column and last colu mn across all column sets. | |
445 // | |
446 // FIXME: Eventually we will know overflow on a per-column basis, but we can 't do this until we have a painting | |
447 // mode that understands not to paint contents from a previous column in the overflow area of a following column. | |
448 // This problem applies to regions and pages as well and is not unique to co lumns. | |
449 bool isFirstColumn = !columnIndex; | |
450 bool isLastColumn = columnIndex == columnCount - 1; | |
451 bool isLTR = m_columnSet.style()->isLeftToRightDirection(); | |
452 bool isLeftmostColumn = isLTR ? isFirstColumn : isLastColumn; | |
453 bool isRightmostColumn = isLTR ? isLastColumn : isFirstColumn; | |
454 | |
455 // Calculate the overflow rectangle, based on the flow thread's, clipped at column logical | |
456 // top/bottom unless it's the first/last column. | |
457 LayoutRect overflowRect = m_columnSet.overflowRectForFlowThreadPortion(porti onRect, isFirstColumn && m_columnSet.isFirstRegion(), isLastColumn && m_columnSe t.isLastRegion()); | |
458 | |
459 // Avoid overflowing into neighboring columns, by clipping in the middle of adjacent column | |
460 // gaps. Also make sure that we avoid rounding errors. | |
461 if (m_columnSet.isHorizontalWritingMode()) { | |
462 if (!isLeftmostColumn) | |
463 overflowRect.shiftXEdgeTo(portionRect.x() - columnGap / 2); | |
464 if (!isRightmostColumn) | |
465 overflowRect.shiftMaxXEdgeTo(portionRect.maxX() + columnGap - column Gap / 2); | |
466 } else { | |
467 if (!isLeftmostColumn) | |
468 overflowRect.shiftYEdgeTo(portionRect.y() - columnGap / 2); | |
469 if (!isRightmostColumn) | |
470 overflowRect.shiftMaxYEdgeTo(portionRect.maxY() + columnGap - column Gap / 2); | |
471 } | |
472 return overflowRect; | |
473 } | |
474 | |
475 unsigned MultiColumnFragmentainerGroup::columnIndexAtOffset(LayoutUnit offsetInF lowThread, ColumnIndexCalculationMode mode) const | |
476 { | |
477 // Handle the offset being out of range. | |
478 if (offsetInFlowThread < m_logicalTopInFlowThread) | |
479 return 0; | |
480 // If we're laying out right now, we cannot constrain against some logical b ottom, since it | |
481 // isn't known yet. Otherwise, just return the last column if we're past the logical bottom. | |
482 if (mode == ClampToExistingColumns) { | |
483 if (offsetInFlowThread >= m_logicalBottomInFlowThread) | |
484 return actualColumnCount() - 1; | |
485 } | |
486 | |
487 if (m_columnHeight) | |
488 return (offsetInFlowThread - m_logicalTopInFlowThread).toFloat() / m_col umnHeight.toFloat(); | |
489 return 0; | |
490 } | |
491 | |
492 MultiColumnFragmentainerGroupList::MultiColumnFragmentainerGroupList(RenderMulti ColumnSet& columnSet) | |
493 : m_columnSet(columnSet) | |
494 { | |
495 append(MultiColumnFragmentainerGroup(m_columnSet)); | |
496 } | |
497 | |
498 MultiColumnFragmentainerGroup& MultiColumnFragmentainerGroupList::addExtraGroup( ) | |
499 { | |
500 append(MultiColumnFragmentainerGroup(m_columnSet)); | |
501 return last(); | |
502 } | |
503 | |
504 void MultiColumnFragmentainerGroupList::deleteExtraGroups() | |
505 { | |
506 shrink(1); | |
507 } | |
508 | |
509 } // namespace blink | |
OLD | NEW |