| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 2012 Apple Inc. All rights reserved. | 2 * Copyright (C) 2012 Apple Inc. All rights reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions | 5 * modification, are permitted provided that the following conditions |
| 6 * are met: | 6 * are met: |
| 7 * 1. Redistributions of source code must retain the above copyright | 7 * 1. Redistributions of source code must retain the above copyright |
| 8 * notice, this list of conditions and the following disclaimer. | 8 * notice, this list of conditions and the following disclaimer. |
| 9 * 2. Redistributions in binary form must reproduce the above copyright | 9 * 2. Redistributions in binary form must reproduce the above copyright |
| 10 * notice, this list of conditions and the following disclaimer in the | 10 * notice, this list of conditions and the following disclaimer in the |
| 11 * documentation and/or other materials provided with the distribution. | 11 * documentation and/or other materials provided with the distribution. |
| 12 * | 12 * |
| 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY | 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR | 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 24 */ | 24 */ |
| 25 | 25 |
| 26 #include "config.h" | 26 #include "config.h" |
| 27 #include "core/rendering/RenderMultiColumnSet.h" | 27 #include "core/rendering/RenderMultiColumnSet.h" |
| 28 | 28 |
| 29 #include "core/layout/MultiColumnFragmentainerGroup.h" |
| 29 #include "core/paint/MultiColumnSetPainter.h" | 30 #include "core/paint/MultiColumnSetPainter.h" |
| 30 #include "core/rendering/RenderMultiColumnFlowThread.h" | 31 #include "core/rendering/RenderMultiColumnFlowThread.h" |
| 31 | 32 |
| 32 namespace blink { | 33 namespace blink { |
| 33 | 34 |
| 34 RenderMultiColumnSet::RenderMultiColumnSet(RenderFlowThread* flowThread) | 35 RenderMultiColumnSet::RenderMultiColumnSet(RenderFlowThread* flowThread) |
| 35 : RenderRegion(0, flowThread) | 36 : RenderRegion(0, flowThread) |
| 36 , m_columnHeight(0) | 37 , m_fragmentainerGroups(*this) |
| 37 , m_maxColumnHeight(RenderFlowThread::maxLogicalHeight()) | |
| 38 , m_minSpaceShortage(RenderFlowThread::maxLogicalHeight()) | |
| 39 , m_minimumColumnHeight(0) | |
| 40 { | 38 { |
| 41 } | 39 } |
| 42 | 40 |
| 43 RenderMultiColumnSet* RenderMultiColumnSet::createAnonymous(RenderFlowThread& fl
owThread, const LayoutStyle& parentStyle) | 41 RenderMultiColumnSet* RenderMultiColumnSet::createAnonymous(RenderFlowThread& fl
owThread, const LayoutStyle& parentStyle) |
| 44 { | 42 { |
| 45 Document& document = flowThread.document(); | 43 Document& document = flowThread.document(); |
| 46 RenderMultiColumnSet* renderer = new RenderMultiColumnSet(&flowThread); | 44 RenderMultiColumnSet* renderer = new RenderMultiColumnSet(&flowThread); |
| 47 renderer->setDocumentForAnonymous(&document); | 45 renderer->setDocumentForAnonymous(&document); |
| 48 renderer->setStyle(LayoutStyle::createAnonymousStyleWithDisplay(parentStyle,
BLOCK)); | 46 renderer->setStyle(LayoutStyle::createAnonymousStyleWithDisplay(parentStyle,
BLOCK)); |
| 49 return renderer; | 47 return renderer; |
| 50 } | 48 } |
| 51 | 49 |
| 50 MultiColumnFragmentainerGroup& RenderMultiColumnSet::fragmentainerGroupAtFlowThr
eadOffset(LayoutUnit) |
| 51 { |
| 52 // FIXME: implement this, once we have support for multiple rows. |
| 53 return m_fragmentainerGroups.first(); |
| 54 } |
| 55 |
| 56 const MultiColumnFragmentainerGroup& RenderMultiColumnSet::fragmentainerGroupAtF
lowThreadOffset(LayoutUnit) const |
| 57 { |
| 58 // FIXME: implement this, once we have support for multiple rows. |
| 59 return m_fragmentainerGroups.first(); |
| 60 } |
| 61 |
| 62 LayoutUnit RenderMultiColumnSet::pageLogicalHeight() const |
| 63 { |
| 64 // FIXME: pageLogicalHeight() needs to take a flow thread offset parameter,
so that we can |
| 65 // locate the right row. |
| 66 return firstFragmentainerGroup().logicalHeight(); |
| 67 } |
| 68 |
| 52 RenderMultiColumnSet* RenderMultiColumnSet::nextSiblingMultiColumnSet() const | 69 RenderMultiColumnSet* RenderMultiColumnSet::nextSiblingMultiColumnSet() const |
| 53 { | 70 { |
| 54 for (LayoutObject* sibling = nextSibling(); sibling; sibling = sibling->next
Sibling()) { | 71 for (LayoutObject* sibling = nextSibling(); sibling; sibling = sibling->next
Sibling()) { |
| 55 if (sibling->isRenderMultiColumnSet()) | 72 if (sibling->isRenderMultiColumnSet()) |
| 56 return toRenderMultiColumnSet(sibling); | 73 return toRenderMultiColumnSet(sibling); |
| 57 } | 74 } |
| 58 return 0; | 75 return 0; |
| 59 } | 76 } |
| 60 | 77 |
| 61 RenderMultiColumnSet* RenderMultiColumnSet::previousSiblingMultiColumnSet() cons
t | 78 RenderMultiColumnSet* RenderMultiColumnSet::previousSiblingMultiColumnSet() cons
t |
| 62 { | 79 { |
| 63 for (LayoutObject* sibling = previousSibling(); sibling; sibling = sibling->
previousSibling()) { | 80 for (LayoutObject* sibling = previousSibling(); sibling; sibling = sibling->
previousSibling()) { |
| 64 if (sibling->isRenderMultiColumnSet()) | 81 if (sibling->isRenderMultiColumnSet()) |
| 65 return toRenderMultiColumnSet(sibling); | 82 return toRenderMultiColumnSet(sibling); |
| 66 } | 83 } |
| 67 return 0; | 84 return 0; |
| 68 } | 85 } |
| 69 | 86 |
| 70 void RenderMultiColumnSet::setLogicalTopInFlowThread(LayoutUnit logicalTop) | 87 LayoutUnit RenderMultiColumnSet::logicalTopInFlowThread() const |
| 71 { | 88 { |
| 72 LayoutRect rect = flowThreadPortionRect(); | 89 return firstFragmentainerGroup().logicalTopInFlowThread(); |
| 73 if (isHorizontalWritingMode()) | |
| 74 rect.setY(logicalTop); | |
| 75 else | |
| 76 rect.setX(logicalTop); | |
| 77 setFlowThreadPortionRect(rect); | |
| 78 } | 90 } |
| 79 | 91 |
| 80 void RenderMultiColumnSet::setLogicalBottomInFlowThread(LayoutUnit logicalBottom
) | 92 LayoutUnit RenderMultiColumnSet::logicalBottomInFlowThread() const |
| 81 { | 93 { |
| 82 LayoutRect rect = flowThreadPortionRect(); | 94 return lastFragmentainerGroup().logicalBottomInFlowThread(); |
| 83 if (isHorizontalWritingMode()) | |
| 84 rect.shiftMaxYEdgeTo(logicalBottom); | |
| 85 else | |
| 86 rect.shiftMaxXEdgeTo(logicalBottom); | |
| 87 setFlowThreadPortionRect(rect); | |
| 88 } | 95 } |
| 89 | 96 |
| 90 bool RenderMultiColumnSet::heightIsAuto() const | 97 bool RenderMultiColumnSet::heightIsAuto() const |
| 91 { | 98 { |
| 92 RenderMultiColumnFlowThread* flowThread = multiColumnFlowThread(); | 99 RenderMultiColumnFlowThread* flowThread = multiColumnFlowThread(); |
| 93 if (!flowThread->isRenderPagedFlowThread()) { | 100 if (!flowThread->isRenderPagedFlowThread()) { |
| 94 if (multiColumnBlockFlow()->style()->columnFill() == ColumnFillBalance) | 101 if (multiColumnBlockFlow()->style()->columnFill() == ColumnFillBalance) |
| 95 return true; | 102 return true; |
| 96 if (RenderBox* next = nextSiblingBox()) { | 103 if (RenderBox* next = nextSiblingBox()) { |
| 97 if (next->isRenderMultiColumnSpannerPlaceholder()) { | 104 if (next->isRenderMultiColumnSpannerPlaceholder()) { |
| 98 // If we're followed by a spanner, we need to balance. | 105 // If we're followed by a spanner, we need to balance. |
| 99 return true; | 106 return true; |
| 100 } | 107 } |
| 101 } | 108 } |
| 102 } | 109 } |
| 103 return !flowThread->columnHeightAvailable(); | 110 return !flowThread->columnHeightAvailable(); |
| 104 } | 111 } |
| 105 | 112 |
| 106 LayoutSize RenderMultiColumnSet::flowThreadTranslationAtOffset(LayoutUnit blockO
ffset) const | 113 LayoutSize RenderMultiColumnSet::flowThreadTranslationAtOffset(LayoutUnit blockO
ffset) const |
| 107 { | 114 { |
| 108 unsigned columnIndex = columnIndexAtOffset(blockOffset); | 115 const MultiColumnFragmentainerGroup& row = fragmentainerGroupAtFlowThreadOff
set(blockOffset); |
| 109 LayoutRect portionRect(flowThreadPortionRectAt(columnIndex)); | 116 return row.offsetFromColumnSet() + row.flowThreadTranslationAtOffset(blockOf
fset); |
| 110 flipForWritingMode(portionRect); | |
| 111 LayoutRect columnRect(columnRectAt(columnIndex)); | |
| 112 flipForWritingMode(columnRect); | |
| 113 return toLayoutPoint(contentBoxOffset()) + columnRect.location() - portionRe
ct.location(); | |
| 114 } | 117 } |
| 115 | 118 |
| 116 LayoutUnit RenderMultiColumnSet::heightAdjustedForSetOffset(LayoutUnit height) c
onst | 119 void RenderMultiColumnSet::updateMinimumColumnHeight(LayoutUnit offsetInFlowThre
ad, LayoutUnit height) |
| 117 { | 120 { |
| 118 // Adjust for the top offset within the content box of the multicol containe
r (containing | 121 fragmentainerGroupAtFlowThreadOffset(offsetInFlowThread).updateMinimumColumn
Height(height); |
| 119 // block), unless this is the first set. We know that the top offset for the
first set will be | |
| 120 // zero, but if the multicol container has non-zero top border or padding, t
he set's top offset | |
| 121 // (initially being 0 and relative to the border box) will be negative until
it has been laid | |
| 122 // out. Had we used this bogus offset, we would calculate the wrong height,
and risk performing | |
| 123 // a wasted layout iteration. Of course all other sets (if any) have this pr
oblem in the first | |
| 124 // layout pass too, but there's really nothing we can do there until the flo
w thread has been | |
| 125 // laid out anyway. | |
| 126 if (previousSiblingMultiColumnSet()) { | |
| 127 RenderBlockFlow* multicolBlock = multiColumnBlockFlow(); | |
| 128 LayoutUnit contentLogicalTop = logicalTop() - multicolBlock->borderAndPa
ddingBefore(); | |
| 129 height -= contentLogicalTop; | |
| 130 } | |
| 131 return max(height, LayoutUnit(1)); // Let's avoid zero height, as that would
probably cause an infinite amount of columns to be created. | |
| 132 } | 122 } |
| 133 | 123 |
| 134 LayoutUnit RenderMultiColumnSet::pageLogicalTopForOffset(LayoutUnit offset) cons
t | 124 LayoutUnit RenderMultiColumnSet::pageLogicalTopForOffset(LayoutUnit offset) cons
t |
| 135 { | 125 { |
| 136 unsigned columnIndex = columnIndexAtOffset(offset, AssumeNewColumns); | 126 return fragmentainerGroupAtFlowThreadOffset(offset).columnLogicalTopForOffse
t(offset); |
| 137 return logicalTopInFlowThread() + columnIndex * pageLogicalHeight(); | |
| 138 } | |
| 139 | |
| 140 void RenderMultiColumnSet::setAndConstrainColumnHeight(LayoutUnit newHeight) | |
| 141 { | |
| 142 m_columnHeight = newHeight; | |
| 143 if (m_columnHeight > m_maxColumnHeight) | |
| 144 m_columnHeight = m_maxColumnHeight; | |
| 145 // FIXME: the height may also be affected by the enclosing pagination contex
t, if any. | |
| 146 } | |
| 147 | |
| 148 unsigned RenderMultiColumnSet::findRunWithTallestColumns() const | |
| 149 { | |
| 150 unsigned indexWithLargestHeight = 0; | |
| 151 LayoutUnit largestHeight; | |
| 152 LayoutUnit previousOffset = logicalTopInFlowThread(); | |
| 153 size_t runCount = m_contentRuns.size(); | |
| 154 ASSERT(runCount); | |
| 155 for (size_t i = 0; i < runCount; i++) { | |
| 156 const ContentRun& run = m_contentRuns[i]; | |
| 157 LayoutUnit height = run.columnLogicalHeight(previousOffset); | |
| 158 if (largestHeight < height) { | |
| 159 largestHeight = height; | |
| 160 indexWithLargestHeight = i; | |
| 161 } | |
| 162 previousOffset = run.breakOffset(); | |
| 163 } | |
| 164 return indexWithLargestHeight; | |
| 165 } | |
| 166 | |
| 167 void RenderMultiColumnSet::distributeImplicitBreaks() | |
| 168 { | |
| 169 #if ENABLE(ASSERT) | |
| 170 // There should be no implicit breaks assumed at this point. | |
| 171 for (unsigned i = 0; i < m_contentRuns.size(); i++) | |
| 172 ASSERT(!m_contentRuns[i].assumedImplicitBreaks()); | |
| 173 #endif // ENABLE(ASSERT) | |
| 174 | |
| 175 // Insert a final content run to encompass all content. This will include ov
erflow if this is | |
| 176 // the last set. | |
| 177 addContentRun(logicalBottomInFlowThread()); | |
| 178 unsigned columnCount = m_contentRuns.size(); | |
| 179 | |
| 180 // If there is room for more breaks (to reach the used value of column-count
), imagine that we | |
| 181 // insert implicit breaks at suitable locations. At any given time, the cont
ent run with the | |
| 182 // currently tallest columns will get another implicit break "inserted", whi
ch will increase its | |
| 183 // column count by one and shrink its columns' height. Repeat until we have
the desired total | |
| 184 // number of breaks. The largest column height among the runs will then be t
he initial column | |
| 185 // height for the balancer to use. | |
| 186 while (columnCount < usedColumnCount()) { | |
| 187 unsigned index = findRunWithTallestColumns(); | |
| 188 m_contentRuns[index].assumeAnotherImplicitBreak(); | |
| 189 columnCount++; | |
| 190 } | |
| 191 } | |
| 192 | |
| 193 LayoutUnit RenderMultiColumnSet::calculateColumnHeight(BalancedHeightCalculation
calculationMode) const | |
| 194 { | |
| 195 if (calculationMode == GuessFromFlowThreadPortion) { | |
| 196 // Initial balancing. Start with the lowest imaginable column height. We
use the tallest | |
| 197 // content run (after having "inserted" implicit breaks), and find its s
tart offset (by | |
| 198 // looking at the previous run's end offset, or, if there's no previous
run, the set's start | |
| 199 // offset in the flow thread). | |
| 200 unsigned index = findRunWithTallestColumns(); | |
| 201 LayoutUnit startOffset = index > 0 ? m_contentRuns[index - 1].breakOffse
t() : logicalTopInFlowThread(); | |
| 202 return std::max<LayoutUnit>(m_contentRuns[index].columnLogicalHeight(sta
rtOffset), m_minimumColumnHeight); | |
| 203 } | |
| 204 | |
| 205 if (actualColumnCount() <= usedColumnCount()) { | |
| 206 // With the current column height, the content fits without creating ove
rflowing columns. We're done. | |
| 207 return m_columnHeight; | |
| 208 } | |
| 209 | |
| 210 if (m_contentRuns.size() >= usedColumnCount()) { | |
| 211 // Too many forced breaks to allow any implicit breaks. Initial balancin
g should already | |
| 212 // have set a good height. There's nothing more we should do. | |
| 213 return m_columnHeight; | |
| 214 } | |
| 215 | |
| 216 // If the initial guessed column height wasn't enough, stretch it now. Stret
ch by the lowest | |
| 217 // amount of space shortage found during layout. | |
| 218 | |
| 219 ASSERT(m_minSpaceShortage > 0); // We should never _shrink_ the height! | |
| 220 ASSERT(m_minSpaceShortage != RenderFlowThread::maxLogicalHeight()); // If th
is happens, we probably have a bug. | |
| 221 if (m_minSpaceShortage == RenderFlowThread::maxLogicalHeight()) | |
| 222 return m_columnHeight; // So bail out rather than looping infinitely. | |
| 223 | |
| 224 return m_columnHeight + m_minSpaceShortage; | |
| 225 } | 127 } |
| 226 | 128 |
| 227 void RenderMultiColumnSet::addContentRun(LayoutUnit endOffsetFromFirstPage) | 129 void RenderMultiColumnSet::addContentRun(LayoutUnit endOffsetFromFirstPage) |
| 228 { | 130 { |
| 229 if (!heightIsAuto()) | 131 if (!heightIsAuto()) |
| 230 return; | 132 return; |
| 231 if (!m_contentRuns.isEmpty() && endOffsetFromFirstPage <= m_contentRuns.last
().breakOffset()) | 133 fragmentainerGroupAtFlowThreadOffset(endOffsetFromFirstPage).addContentRun(e
ndOffsetFromFirstPage); |
| 232 return; | |
| 233 // Append another item as long as we haven't exceeded used column count. Wha
t ends up in the | |
| 234 // overflow area shouldn't affect column balancing. | |
| 235 if (m_contentRuns.size() < usedColumnCount()) | |
| 236 m_contentRuns.append(ContentRun(endOffsetFromFirstPage)); | |
| 237 } | 134 } |
| 238 | 135 |
| 239 bool RenderMultiColumnSet::recalculateColumnHeight(BalancedHeightCalculation cal
culationMode) | 136 bool RenderMultiColumnSet::recalculateColumnHeight(BalancedColumnHeightCalculati
on calculationMode) |
| 240 { | 137 { |
| 241 LayoutUnit oldColumnHeight = m_columnHeight; | 138 bool changed = false; |
| 242 | 139 for (auto& group : m_fragmentainerGroups) |
| 243 m_maxColumnHeight = calculateMaxColumnHeight(); | 140 changed = group.recalculateColumnHeight(calculationMode) || changed; |
| 244 | 141 return changed; |
| 245 if (heightIsAuto()) { | |
| 246 if (calculationMode == GuessFromFlowThreadPortion) { | |
| 247 // Post-process the content runs and find out where the implicit bre
aks will occur. | |
| 248 distributeImplicitBreaks(); | |
| 249 } | |
| 250 LayoutUnit newColumnHeight = calculateColumnHeight(calculationMode); | |
| 251 setAndConstrainColumnHeight(newColumnHeight); | |
| 252 // After having calculated an initial column height, the multicol contai
ner typically needs at | |
| 253 // least one more layout pass with a new column height, but if a height
was specified, we only | |
| 254 // need to do this if we think that we need less space than specified. C
onversely, if we | |
| 255 // determined that the columns need to be as tall as the specified heigh
t of the container, we | |
| 256 // have already laid it out correctly, and there's no need for another p
ass. | |
| 257 } else { | |
| 258 // The position of the column set may have changed, in which case height
available for | |
| 259 // columns may have changed as well. | |
| 260 setAndConstrainColumnHeight(m_columnHeight); | |
| 261 } | |
| 262 | |
| 263 // We can get rid of the content runs now, if we haven't already done so. Th
ey are only needed | |
| 264 // to calculate the initial balanced column height. In fact, we have to get
rid of them before | |
| 265 // the next layout pass, since each pass will rebuild this. | |
| 266 m_contentRuns.clear(); | |
| 267 | |
| 268 if (m_columnHeight == oldColumnHeight) | |
| 269 return false; // No change. We're done. | |
| 270 | |
| 271 m_minSpaceShortage = RenderFlowThread::maxLogicalHeight(); | |
| 272 return true; // Need another pass. | |
| 273 } | 142 } |
| 274 | 143 |
| 275 void RenderMultiColumnSet::recordSpaceShortage(LayoutUnit spaceShortage) | 144 void RenderMultiColumnSet::recordSpaceShortage(LayoutUnit offsetInFlowThread, La
youtUnit spaceShortage) |
| 276 { | 145 { |
| 277 if (spaceShortage >= m_minSpaceShortage) | 146 fragmentainerGroupAtFlowThreadOffset(offsetInFlowThread).recordSpaceShortage
(spaceShortage); |
| 278 return; | |
| 279 | |
| 280 // The space shortage is what we use as our stretch amount. We need a positi
ve number here in | |
| 281 // order to get anywhere. | |
| 282 ASSERT(spaceShortage > 0); | |
| 283 | |
| 284 m_minSpaceShortage = spaceShortage; | |
| 285 } | 147 } |
| 286 | 148 |
| 287 void RenderMultiColumnSet::resetColumnHeight() | 149 void RenderMultiColumnSet::resetColumnHeight() |
| 288 { | 150 { |
| 289 // Nuke previously stored minimum column height. Contents may have changed f
or all we know. | 151 m_fragmentainerGroups.deleteExtraGroups(); |
| 290 m_minimumColumnHeight = 0; | 152 m_fragmentainerGroups.first().resetColumnHeight(); |
| 153 } |
| 291 | 154 |
| 292 m_maxColumnHeight = calculateMaxColumnHeight(); | 155 void RenderMultiColumnSet::beginFlow(LayoutUnit offsetInFlowThread) |
| 156 { |
| 157 // At this point layout is exactly at the beginning of this set. Store block
offset from flow |
| 158 // thread start. |
| 159 m_fragmentainerGroups.first().setLogicalTopInFlowThread(offsetInFlowThread); |
| 160 } |
| 293 | 161 |
| 294 LayoutUnit oldColumnHeight = pageLogicalHeight(); | 162 void RenderMultiColumnSet::endFlow(LayoutUnit offsetInFlowThread) |
| 295 | 163 { |
| 296 if (heightIsAuto()) | 164 // At this point layout is exactly at the end of this set. Store block offse
t from flow thread |
| 297 m_columnHeight = 0; | 165 // start. This set is now considered "flowed", although we may have to revis
it it later (with |
| 298 else | 166 // beginFlow()), e.g. if a subtree in the flow thread has to be laid out ove
r again because the |
| 299 setAndConstrainColumnHeight(heightAdjustedForSetOffset(multiColumnFlowTh
read()->columnHeightAvailable())); | 167 // initial margin collapsing estimates were wrong. |
| 300 | 168 m_fragmentainerGroups.last().setLogicalBottomInFlowThread(offsetInFlowThread
); |
| 301 if (pageLogicalHeight() != oldColumnHeight) | |
| 302 setChildNeedsLayout(MarkOnlyThis); | |
| 303 | |
| 304 // Content runs are only needed in the initial layout pass, in order to find
an initial column | |
| 305 // height, and should have been deleted afterwards. We're about to rebuild t
he content runs, so | |
| 306 // the list needs to be empty. | |
| 307 ASSERT(m_contentRuns.isEmpty()); | |
| 308 } | 169 } |
| 309 | 170 |
| 310 void RenderMultiColumnSet::expandToEncompassFlowThreadContentsIfNeeded() | 171 void RenderMultiColumnSet::expandToEncompassFlowThreadContentsIfNeeded() |
| 311 { | 172 { |
| 312 ASSERT(multiColumnFlowThread()->lastMultiColumnSet() == this); | 173 ASSERT(multiColumnFlowThread()->lastMultiColumnSet() == this); |
| 313 LayoutRect rect(flowThreadPortionRect()); | 174 // FIXME: this may result in the need for creating additional rows, since th
ere may not be |
| 314 | 175 // enough space remaining in the currently last row. |
| 315 // Get the offset within the flow thread in its block progression direction.
Then get the | 176 m_fragmentainerGroups.last().expandToEncompassFlowThreadOverflow(); |
| 316 // flow thread's remaining logical height including its overflow and expand
our rect | |
| 317 // to encompass that remaining height and overflow. The idea is that we will
generate | |
| 318 // additional columns and pages to hold that overflow, since people do write
bad | |
| 319 // content like <body style="height:0px"> in multi-column layouts. | |
| 320 bool isHorizontal = flowThread()->isHorizontalWritingMode(); | |
| 321 LayoutUnit logicalTopOffset = isHorizontal ? rect.y() : rect.x(); | |
| 322 LayoutRect layoutRect = flowThread()->layoutOverflowRect(); | |
| 323 LayoutUnit logicalHeightWithOverflow = (isHorizontal ? layoutRect.maxY() : l
ayoutRect.maxX()) - logicalTopOffset; | |
| 324 setFlowThreadPortionRect(LayoutRect(rect.x(), rect.y(), isHorizontal ? rect.
width() : logicalHeightWithOverflow, isHorizontal ? logicalHeightWithOverflow :
rect.height())); | |
| 325 } | 177 } |
| 326 | 178 |
| 327 void RenderMultiColumnSet::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTo
p, LogicalExtentComputedValues& computedValues) const | 179 void RenderMultiColumnSet::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTo
p, LogicalExtentComputedValues& computedValues) const |
| 328 { | 180 { |
| 329 computedValues.m_extent = m_columnHeight; | 181 LayoutUnit logicalHeight; |
| 182 for (const auto& group : m_fragmentainerGroups) |
| 183 logicalHeight += group.logicalHeight(); |
| 184 computedValues.m_extent = logicalHeight; |
| 330 computedValues.m_position = logicalTop; | 185 computedValues.m_position = logicalTop; |
| 331 } | 186 } |
| 332 | 187 |
| 333 LayoutUnit RenderMultiColumnSet::calculateMaxColumnHeight() const | |
| 334 { | |
| 335 RenderBlockFlow* multicolBlock = multiColumnBlockFlow(); | |
| 336 const LayoutStyle& multicolStyle = multicolBlock->styleRef(); | |
| 337 LayoutUnit availableHeight = multiColumnFlowThread()->columnHeightAvailable(
); | |
| 338 LayoutUnit maxColumnHeight = availableHeight ? availableHeight : RenderFlowT
hread::maxLogicalHeight(); | |
| 339 if (!multicolStyle.logicalMaxHeight().isMaxSizeNone()) { | |
| 340 LayoutUnit logicalMaxHeight = multicolBlock->computeContentLogicalHeight
(multicolStyle.logicalMaxHeight(), -1); | |
| 341 if (logicalMaxHeight != -1 && maxColumnHeight > logicalMaxHeight) | |
| 342 maxColumnHeight = logicalMaxHeight; | |
| 343 } | |
| 344 return heightAdjustedForSetOffset(maxColumnHeight); | |
| 345 } | |
| 346 | |
| 347 LayoutUnit RenderMultiColumnSet::columnGap() const | 188 LayoutUnit RenderMultiColumnSet::columnGap() const |
| 348 { | 189 { |
| 349 RenderBlockFlow* parentBlock = multiColumnBlockFlow(); | 190 RenderBlockFlow* parentBlock = multiColumnBlockFlow(); |
| 350 if (parentBlock->style()->hasNormalColumnGap()) | 191 if (parentBlock->style()->hasNormalColumnGap()) |
| 351 return parentBlock->style()->fontDescription().computedPixelSize(); // "
1em" is recommended as the normal gap setting. Matches <p> margins. | 192 return parentBlock->style()->fontDescription().computedPixelSize(); // "
1em" is recommended as the normal gap setting. Matches <p> margins. |
| 352 return parentBlock->style()->columnGap(); | 193 return parentBlock->style()->columnGap(); |
| 353 } | 194 } |
| 354 | 195 |
| 355 unsigned RenderMultiColumnSet::actualColumnCount() const | 196 unsigned RenderMultiColumnSet::actualColumnCount() const |
| 356 { | 197 { |
| 357 // We must always return a value of 1 or greater. Column count = 0 is a mean
ingless situation, | 198 // FIXME: remove this method. It's a meaningless question to ask the set "ho
w many columns do |
| 358 // and will confuse and cause problems in other parts of the code. | 199 // you actually have?", since that may vary for each row. |
| 359 if (!pageLogicalHeight()) | 200 return firstFragmentainerGroup().actualColumnCount(); |
| 360 return 1; | |
| 361 | |
| 362 // Our portion rect determines our column count. We have as many columns as
needed to fit all the content. | |
| 363 LayoutUnit logicalHeightInColumns = flowThread()->isHorizontalWritingMode()
? flowThreadPortionRect().height() : flowThreadPortionRect().width(); | |
| 364 if (!logicalHeightInColumns) | |
| 365 return 1; | |
| 366 | |
| 367 unsigned count = ceil(logicalHeightInColumns.toFloat() / pageLogicalHeight()
.toFloat()); | |
| 368 ASSERT(count >= 1); | |
| 369 return count; | |
| 370 } | |
| 371 | |
| 372 LayoutRect RenderMultiColumnSet::columnRectAt(unsigned index) const | |
| 373 { | |
| 374 LayoutUnit colLogicalWidth = pageLogicalWidth(); | |
| 375 LayoutUnit colLogicalHeight = pageLogicalHeight(); | |
| 376 LayoutUnit colLogicalTop = borderBefore() + paddingBefore(); | |
| 377 LayoutUnit colLogicalLeft = borderAndPaddingLogicalLeft(); | |
| 378 LayoutUnit colGap = columnGap(); | |
| 379 | |
| 380 if (multiColumnFlowThread()->progressionIsInline()) { | |
| 381 if (style()->isLeftToRightDirection()) | |
| 382 colLogicalLeft += index * (colLogicalWidth + colGap); | |
| 383 else | |
| 384 colLogicalLeft += contentLogicalWidth() - colLogicalWidth - index *
(colLogicalWidth + colGap); | |
| 385 } else { | |
| 386 colLogicalTop += index * (colLogicalHeight + colGap); | |
| 387 } | |
| 388 | |
| 389 if (isHorizontalWritingMode()) | |
| 390 return LayoutRect(colLogicalLeft, colLogicalTop, colLogicalWidth, colLog
icalHeight); | |
| 391 return LayoutRect(colLogicalTop, colLogicalLeft, colLogicalHeight, colLogica
lWidth); | |
| 392 } | |
| 393 | |
| 394 unsigned RenderMultiColumnSet::columnIndexAtOffset(LayoutUnit offset, ColumnInde
xCalculationMode mode) const | |
| 395 { | |
| 396 LayoutRect portionRect(flowThreadPortionRect()); | |
| 397 | |
| 398 // Handle the offset being out of range. | |
| 399 LayoutUnit flowThreadLogicalTop = isHorizontalWritingMode() ? portionRect.y(
) : portionRect.x(); | |
| 400 if (offset < flowThreadLogicalTop) | |
| 401 return 0; | |
| 402 // If we're laying out right now, we cannot constrain against some logical b
ottom, since it | |
| 403 // isn't known yet. Otherwise, just return the last column if we're past the
logical bottom. | |
| 404 if (mode == ClampToExistingColumns) { | |
| 405 LayoutUnit flowThreadLogicalBottom = isHorizontalWritingMode() ? portion
Rect.maxY() : portionRect.maxX(); | |
| 406 if (offset >= flowThreadLogicalBottom) | |
| 407 return actualColumnCount() - 1; | |
| 408 } | |
| 409 | |
| 410 if (LayoutUnit pageLogicalHeight = this->pageLogicalHeight()) | |
| 411 return (offset - flowThreadLogicalTop).toFloat() / pageLogicalHeight.toF
loat(); | |
| 412 | |
| 413 return 0; | |
| 414 } | |
| 415 | |
| 416 LayoutRect RenderMultiColumnSet::flowThreadPortionRectAt(unsigned index) const | |
| 417 { | |
| 418 LayoutRect portionRect = flowThreadPortionRect(); | |
| 419 if (isHorizontalWritingMode()) | |
| 420 portionRect = LayoutRect(portionRect.x(), portionRect.y() + index * page
LogicalHeight(), portionRect.width(), pageLogicalHeight()); | |
| 421 else | |
| 422 portionRect = LayoutRect(portionRect.x() + index * pageLogicalHeight(),
portionRect.y(), pageLogicalHeight(), portionRect.height()); | |
| 423 return portionRect; | |
| 424 } | |
| 425 | |
| 426 LayoutRect RenderMultiColumnSet::flowThreadPortionOverflowRect(const LayoutRect&
portionRect, unsigned index, unsigned colCount, LayoutUnit colGap) const | |
| 427 { | |
| 428 // This function determines the portion of the flow thread that paints for t
he column. Along the inline axis, columns are | |
| 429 // unclipped at outside edges (i.e., the first and last column in the set),
and they clip to half the column | |
| 430 // gap along interior edges. | |
| 431 // | |
| 432 // In the block direction, we will not clip overflow out of the top of the f
irst column, or out of the bottom of | |
| 433 // the last column. This applies only to the true first column and last colu
mn across all column sets. | |
| 434 // | |
| 435 // FIXME: Eventually we will know overflow on a per-column basis, but we can
't do this until we have a painting | |
| 436 // mode that understands not to paint contents from a previous column in the
overflow area of a following column. | |
| 437 // This problem applies to regions and pages as well and is not unique to co
lumns. | |
| 438 bool isFirstColumn = !index; | |
| 439 bool isLastColumn = index == colCount - 1; | |
| 440 bool isLeftmostColumn = style()->isLeftToRightDirection() ? isFirstColumn :
isLastColumn; | |
| 441 bool isRightmostColumn = style()->isLeftToRightDirection() ? isLastColumn :
isFirstColumn; | |
| 442 | |
| 443 // Calculate the overflow rectangle, based on the flow thread's, clipped at
column logical | |
| 444 // top/bottom unless it's the first/last column. | |
| 445 LayoutRect overflowRect = overflowRectForFlowThreadPortion(portionRect, isFi
rstColumn && isFirstRegion(), isLastColumn && isLastRegion()); | |
| 446 | |
| 447 // Avoid overflowing into neighboring columns, by clipping in the middle of
adjacent column | |
| 448 // gaps. Also make sure that we avoid rounding errors. | |
| 449 if (isHorizontalWritingMode()) { | |
| 450 if (!isLeftmostColumn) | |
| 451 overflowRect.shiftXEdgeTo(portionRect.x() - colGap / 2); | |
| 452 if (!isRightmostColumn) | |
| 453 overflowRect.shiftMaxXEdgeTo(portionRect.maxX() + colGap - colGap /
2); | |
| 454 } else { | |
| 455 if (!isLeftmostColumn) | |
| 456 overflowRect.shiftYEdgeTo(portionRect.y() - colGap / 2); | |
| 457 if (!isRightmostColumn) | |
| 458 overflowRect.shiftMaxYEdgeTo(portionRect.maxY() + colGap - colGap /
2); | |
| 459 } | |
| 460 return overflowRect; | |
| 461 } | 201 } |
| 462 | 202 |
| 463 void RenderMultiColumnSet::paintObject(const PaintInfo& paintInfo, const LayoutP
oint& paintOffset) | 203 void RenderMultiColumnSet::paintObject(const PaintInfo& paintInfo, const LayoutP
oint& paintOffset) |
| 464 { | 204 { |
| 465 MultiColumnSetPainter(*this).paintObject(paintInfo, paintOffset); | 205 MultiColumnSetPainter(*this).paintObject(paintInfo, paintOffset); |
| 466 } | 206 } |
| 467 | 207 |
| 468 void RenderMultiColumnSet::collectLayerFragments(LayerFragments& fragments, cons
t LayoutRect& layerBoundingBox, const LayoutRect& dirtyRect) | 208 void RenderMultiColumnSet::collectLayerFragments(LayerFragments& fragments, cons
t LayoutRect& layerBoundingBox, const LayoutRect& dirtyRect) |
| 469 { | 209 { |
| 470 // |layerBoundingBox| is in the flow thread coordinate space, relative to th
e top/left edge of | 210 for (const auto& group : m_fragmentainerGroups) |
| 471 // the flow thread, but note that it has been converted with respect to writ
ing mode (so that | 211 group.collectLayerFragments(fragments, layerBoundingBox, dirtyRect); |
| 472 // it's visual/physical in that sense). | |
| 473 // | |
| 474 // |dirtyRect| is visual, relative to the multicol container. | |
| 475 // | |
| 476 // Then there's the output from this method - the stuff we put into the list
of fragments. The | |
| 477 // fragment.paginationOffset point is the actual visual translation required
to get from a | |
| 478 // location in the flow thread to a location in a given column. The fragment
.paginationClip | |
| 479 // rectangle, on the other hand, is in flow thread coordinates. | |
| 480 // | |
| 481 // All other rectangles in this method are sized physically, and the inline
direction coordinate | |
| 482 // is physical too, but the block direction coordinate is "logical top". Thi
s is the same as | |
| 483 // e.g. RenderBox::frameRect(). These rectangles also pretend that there's o
nly one long column, | |
| 484 // i.e. they are for the flow thread. | |
| 485 | |
| 486 // Put the layer bounds into flow thread-local coordinates by flipping it fi
rst. Since we're in | |
| 487 // a renderer, most rectangles are represented this way. | |
| 488 LayoutRect layerBoundsInFlowThread(layerBoundingBox); | |
| 489 flowThread()->flipForWritingMode(layerBoundsInFlowThread); | |
| 490 | |
| 491 // Now we can compare with the flow thread portions owned by each column. Fi
rst let's | |
| 492 // see if the rect intersects our flow thread portion at all. | |
| 493 LayoutRect clippedRect(layerBoundsInFlowThread); | |
| 494 clippedRect.intersect(RenderRegion::flowThreadPortionOverflowRect()); | |
| 495 if (clippedRect.isEmpty()) | |
| 496 return; | |
| 497 | |
| 498 // Now we know we intersect at least one column. Let's figure out the logica
l top and logical | |
| 499 // bottom of the area we're checking. | |
| 500 LayoutUnit layerLogicalTop = isHorizontalWritingMode() ? layerBoundsInFlowTh
read.y() : layerBoundsInFlowThread.x(); | |
| 501 LayoutUnit layerLogicalBottom = (isHorizontalWritingMode() ? layerBoundsInFl
owThread.maxY() : layerBoundsInFlowThread.maxX()) - 1; | |
| 502 | |
| 503 // Figure out the start and end columns and only check within that range so
that we don't walk the | |
| 504 // entire column set. | |
| 505 unsigned startColumn = columnIndexAtOffset(layerLogicalTop); | |
| 506 unsigned endColumn = columnIndexAtOffset(layerLogicalBottom); | |
| 507 | |
| 508 LayoutUnit colLogicalWidth = pageLogicalWidth(); | |
| 509 LayoutUnit colGap = columnGap(); | |
| 510 unsigned colCount = actualColumnCount(); | |
| 511 | |
| 512 RenderMultiColumnFlowThread* flowThread = multiColumnFlowThread(); | |
| 513 bool progressionIsInline = flowThread->progressionIsInline(); | |
| 514 bool leftToRight = style()->isLeftToRightDirection(); | |
| 515 | |
| 516 LayoutUnit initialBlockOffset = logicalTop() - flowThread->logicalTop(); | |
| 517 | |
| 518 for (unsigned i = startColumn; i <= endColumn; i++) { | |
| 519 // Get the portion of the flow thread that corresponds to this column. | |
| 520 LayoutRect flowThreadPortion = flowThreadPortionRectAt(i); | |
| 521 | |
| 522 // Now get the overflow rect that corresponds to the column. | |
| 523 LayoutRect flowThreadOverflowPortion = flowThreadPortionOverflowRect(flo
wThreadPortion, i, colCount, colGap); | |
| 524 | |
| 525 // In order to create a fragment we must intersect the portion painted b
y this column. | |
| 526 LayoutRect clippedRect(layerBoundsInFlowThread); | |
| 527 clippedRect.intersect(flowThreadOverflowPortion); | |
| 528 if (clippedRect.isEmpty()) | |
| 529 continue; | |
| 530 | |
| 531 // We also need to intersect the dirty rect. We have to apply a translat
ion and shift based off | |
| 532 // our column index. | |
| 533 LayoutPoint translationOffset; | |
| 534 LayoutUnit inlineOffset = progressionIsInline ? i * (colLogicalWidth + c
olGap) : LayoutUnit(); | |
| 535 if (!leftToRight) | |
| 536 inlineOffset = -inlineOffset; | |
| 537 translationOffset.setX(inlineOffset); | |
| 538 LayoutUnit blockOffset; | |
| 539 if (progressionIsInline) { | |
| 540 blockOffset = initialBlockOffset + (isHorizontalWritingMode() ? -flo
wThreadPortion.y() : -flowThreadPortion.x()); | |
| 541 } else { | |
| 542 // Column gap can apply in the block direction for page fragmentaine
rs. | |
| 543 // There is currently no spec which calls for column-gap to apply | |
| 544 // for page fragmentainers at all, but it's applied here for compati
bility | |
| 545 // with the old multicolumn implementation. | |
| 546 blockOffset = i * colGap; | |
| 547 } | |
| 548 if (isFlippedBlocksWritingMode(style()->writingMode())) | |
| 549 blockOffset = -blockOffset; | |
| 550 translationOffset.setY(blockOffset); | |
| 551 if (!isHorizontalWritingMode()) | |
| 552 translationOffset = translationOffset.transposedPoint(); | |
| 553 // FIXME: The translation needs to include the multicolumn set's content
offset within the | |
| 554 // multicolumn block as well. This won't be an issue until we start crea
ting multiple multicolumn sets. | |
| 555 | |
| 556 // Shift the dirty rect to be in flow thread coordinates with this trans
lation applied. | |
| 557 LayoutRect translatedDirtyRect(dirtyRect); | |
| 558 translatedDirtyRect.moveBy(-translationOffset); | |
| 559 | |
| 560 // See if we intersect the dirty rect. | |
| 561 clippedRect = layerBoundingBox; | |
| 562 clippedRect.intersect(translatedDirtyRect); | |
| 563 if (clippedRect.isEmpty()) | |
| 564 continue; | |
| 565 | |
| 566 // Something does need to paint in this column. Make a fragment now and
supply the physical translation | |
| 567 // offset and the clip rect for the column with that offset applied. | |
| 568 LayerFragment fragment; | |
| 569 fragment.paginationOffset = translationOffset; | |
| 570 | |
| 571 LayoutRect flippedFlowThreadOverflowPortion(flowThreadOverflowPortion); | |
| 572 // Flip it into more a physical (Layer-style) rectangle. | |
| 573 flowThread->flipForWritingMode(flippedFlowThreadOverflowPortion); | |
| 574 fragment.paginationClip = flippedFlowThreadOverflowPortion; | |
| 575 fragments.append(fragment); | |
| 576 } | |
| 577 } | 212 } |
| 578 | 213 |
| 579 void RenderMultiColumnSet::addOverflowFromChildren() | 214 void RenderMultiColumnSet::addOverflowFromChildren() |
| 580 { | 215 { |
| 581 unsigned colCount = actualColumnCount(); | 216 LayoutRect overflowRect; |
| 582 if (!colCount) | 217 for (const auto& group : m_fragmentainerGroups) { |
| 583 return; | 218 LayoutRect rect = group.calculateOverflow(); |
| 584 | 219 rect.move(group.offsetFromColumnSet()); |
| 585 LayoutRect lastRect = columnRectAt(colCount - 1); | 220 overflowRect.unite(rect); |
| 586 addLayoutOverflow(lastRect); | 221 } |
| 222 addLayoutOverflow(overflowRect); |
| 587 if (!hasOverflowClip()) | 223 if (!hasOverflowClip()) |
| 588 addVisualOverflow(lastRect); | 224 addVisualOverflow(overflowRect); |
| 589 } | 225 } |
| 590 | 226 |
| 591 const char* RenderMultiColumnSet::renderName() const | 227 const char* RenderMultiColumnSet::renderName() const |
| 592 { | 228 { |
| 593 return "RenderMultiColumnSet"; | 229 return "RenderMultiColumnSet"; |
| 594 } | 230 } |
| 595 | 231 |
| 596 void RenderMultiColumnSet::insertedIntoTree() | 232 void RenderMultiColumnSet::insertedIntoTree() |
| 597 { | 233 { |
| 598 RenderRegion::insertedIntoTree(); | 234 RenderRegion::insertedIntoTree(); |
| (...skipping 24 matching lines...) Expand all Loading... |
| 623 } | 259 } |
| 624 | 260 |
| 625 void RenderMultiColumnSet::detachRegion() | 261 void RenderMultiColumnSet::detachRegion() |
| 626 { | 262 { |
| 627 if (m_flowThread) { | 263 if (m_flowThread) { |
| 628 m_flowThread->removeRegionFromThread(this); | 264 m_flowThread->removeRegionFromThread(this); |
| 629 m_flowThread = 0; | 265 m_flowThread = 0; |
| 630 } | 266 } |
| 631 } | 267 } |
| 632 | 268 |
| 269 LayoutRect RenderMultiColumnSet::flowThreadPortionRect() const |
| 270 { |
| 271 LayoutRect portionRect(LayoutUnit(), logicalTopInFlowThread(), pageLogicalWi
dth(), logicalHeightInFlowThread()); |
| 272 if (!isHorizontalWritingMode()) |
| 273 return portionRect.transposedRect(); |
| 274 return portionRect; |
| 633 } | 275 } |
| 276 |
| 277 } |
| OLD | NEW |