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/paint/MultiColumnSetPainter.h" | 29 #include "core/paint/MultiColumnSetPainter.h" |
30 #include "core/rendering/MultiColumnRow.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_columnRow(*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, RenderStyle* parentStyle) | 41 RenderMultiColumnSet* RenderMultiColumnSet::createAnonymous(RenderFlowThread* fl owThread, RenderStyle* 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(RenderStyle::createAnonymousStyleWithDisplay(parentStyle, BLOCK)); | 46 renderer->setStyle(RenderStyle::createAnonymousStyleWithDisplay(parentStyle, BLOCK)); |
49 return renderer; | 47 return renderer; |
50 } | 48 } |
51 | 49 |
50 LayoutUnit RenderMultiColumnSet::pageLogicalHeight() const | |
51 { | |
52 // FIXME: pageLogicalHeight() needs to take a flow thread offset parameter, so that we can | |
53 // locate the right row. | |
54 return firstColumnRow()->logicalHeight(); | |
55 } | |
56 | |
52 RenderMultiColumnSet* RenderMultiColumnSet::nextSiblingMultiColumnSet() const | 57 RenderMultiColumnSet* RenderMultiColumnSet::nextSiblingMultiColumnSet() const |
53 { | 58 { |
54 for (RenderObject* sibling = nextSibling(); sibling; sibling = sibling->next Sibling()) { | 59 for (RenderObject* sibling = nextSibling(); sibling; sibling = sibling->next Sibling()) { |
55 if (sibling->isRenderMultiColumnSet()) | 60 if (sibling->isRenderMultiColumnSet()) |
56 return toRenderMultiColumnSet(sibling); | 61 return toRenderMultiColumnSet(sibling); |
57 } | 62 } |
58 return 0; | 63 return 0; |
59 } | 64 } |
60 | 65 |
61 RenderMultiColumnSet* RenderMultiColumnSet::previousSiblingMultiColumnSet() cons t | 66 RenderMultiColumnSet* RenderMultiColumnSet::previousSiblingMultiColumnSet() cons t |
62 { | 67 { |
63 for (RenderObject* sibling = previousSibling(); sibling; sibling = sibling-> previousSibling()) { | 68 for (RenderObject* sibling = previousSibling(); sibling; sibling = sibling-> previousSibling()) { |
64 if (sibling->isRenderMultiColumnSet()) | 69 if (sibling->isRenderMultiColumnSet()) |
65 return toRenderMultiColumnSet(sibling); | 70 return toRenderMultiColumnSet(sibling); |
66 } | 71 } |
67 return 0; | 72 return 0; |
68 } | 73 } |
69 | 74 |
70 void RenderMultiColumnSet::setLogicalTopInFlowThread(LayoutUnit logicalTop) | 75 LayoutUnit RenderMultiColumnSet::logicalTopInFlowThread() const |
71 { | 76 { |
72 LayoutRect rect = flowThreadPortionRect(); | 77 return firstColumnRow()->logicalTopInFlowThread(); |
73 if (isHorizontalWritingMode()) | |
74 rect.setY(logicalTop); | |
75 else | |
76 rect.setX(logicalTop); | |
77 setFlowThreadPortionRect(rect); | |
78 } | 78 } |
79 | 79 |
80 void RenderMultiColumnSet::setLogicalBottomInFlowThread(LayoutUnit logicalBottom ) | 80 LayoutUnit RenderMultiColumnSet::logicalBottomInFlowThread() const |
81 { | 81 { |
82 LayoutRect rect = flowThreadPortionRect(); | 82 return lastColumnRow()->logicalBottomInFlowThread(); |
83 if (isHorizontalWritingMode()) | |
84 rect.shiftMaxYEdgeTo(logicalBottom); | |
85 else | |
86 rect.shiftMaxXEdgeTo(logicalBottom); | |
87 setFlowThreadPortionRect(rect); | |
88 } | 83 } |
89 | 84 |
90 bool RenderMultiColumnSet::heightIsAuto() const | 85 bool RenderMultiColumnSet::heightIsAuto() const |
91 { | 86 { |
92 RenderMultiColumnFlowThread* flowThread = multiColumnFlowThread(); | 87 RenderMultiColumnFlowThread* flowThread = multiColumnFlowThread(); |
93 if (!flowThread->isRenderPagedFlowThread()) { | 88 if (!flowThread->isRenderPagedFlowThread()) { |
94 if (multiColumnBlockFlow()->style()->columnFill() == ColumnFillBalance) | 89 if (multiColumnBlockFlow()->style()->columnFill() == ColumnFillBalance) |
95 return true; | 90 return true; |
96 if (RenderBox* next = nextSiblingBox()) { | 91 if (RenderBox* next = nextSiblingBox()) { |
97 if (next->isRenderMultiColumnSpannerPlaceholder()) { | 92 if (next->isRenderMultiColumnSpannerPlaceholder()) { |
98 // If we're followed by a spanner, we need to balance. | 93 // If we're followed by a spanner, we need to balance. |
99 return true; | 94 return true; |
100 } | 95 } |
101 } | 96 } |
102 } | 97 } |
103 return !flowThread->columnHeightAvailable(); | 98 return !flowThread->columnHeightAvailable(); |
104 } | 99 } |
105 | 100 |
106 LayoutSize RenderMultiColumnSet::flowThreadTranslationAtOffset(LayoutUnit blockO ffset) const | 101 LayoutSize RenderMultiColumnSet::flowThreadTranslationAtOffset(LayoutUnit blockO ffset) const |
107 { | 102 { |
108 unsigned columnIndex = columnIndexAtOffset(blockOffset); | 103 const MultiColumnRow* row = columnRowAtFlowThreadOffset(blockOffset); |
109 LayoutRect portionRect(flowThreadPortionRectAt(columnIndex)); | 104 return row->offsetFromColumnSet() + row->flowThreadTranslationAtOffset(block Offset); |
110 flipForWritingMode(portionRect); | |
111 LayoutRect columnRect(columnRectAt(columnIndex)); | |
112 flipForWritingMode(columnRect); | |
113 return toLayoutPoint(contentBoxOffset()) + columnRect.location() - portionRe ct.location(); | |
114 } | 105 } |
115 | 106 |
116 LayoutUnit RenderMultiColumnSet::heightAdjustedForSetOffset(LayoutUnit height) c onst | 107 void RenderMultiColumnSet::updateMinimumColumnHeight(LayoutUnit offsetInFlowThre ad, LayoutUnit height) |
117 { | 108 { |
118 // Adjust for the top offset within the content box of the multicol containe r (containing | 109 columnRowAtFlowThreadOffset(offsetInFlowThread)->updateMinimumColumnHeight(h eight); |
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 } | 110 } |
133 | 111 |
134 LayoutUnit RenderMultiColumnSet::pageLogicalTopForOffset(LayoutUnit offset) cons t | 112 LayoutUnit RenderMultiColumnSet::pageLogicalTopForOffset(LayoutUnit offset) cons t |
135 { | 113 { |
136 unsigned columnIndex = columnIndexAtOffset(offset, AssumeNewColumns); | 114 return columnRowAtFlowThreadOffset(offset)->columnLogicalTopForOffset(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 } | 115 } |
226 | 116 |
227 void RenderMultiColumnSet::addContentRun(LayoutUnit endOffsetFromFirstPage) | 117 void RenderMultiColumnSet::addContentRun(LayoutUnit endOffsetFromFirstPage) |
228 { | 118 { |
229 if (!heightIsAuto()) | 119 if (!heightIsAuto()) |
230 return; | 120 return; |
231 if (!m_contentRuns.isEmpty() && endOffsetFromFirstPage <= m_contentRuns.last ().breakOffset()) | 121 columnRowAtFlowThreadOffset(endOffsetFromFirstPage)->addContentRun(endOffset FromFirstPage); |
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 } | 122 } |
238 | 123 |
239 bool RenderMultiColumnSet::recalculateColumnHeight(BalancedHeightCalculation cal culationMode) | 124 bool RenderMultiColumnSet::recalculateColumnHeight(BalancedColumnHeightCalculati on calculationMode) |
240 { | 125 { |
241 LayoutUnit oldColumnHeight = m_columnHeight; | 126 bool changed = false; |
242 | 127 for (MultiColumnRow* row = firstColumnRow(); row; row = row->nextRow()) |
243 m_maxColumnHeight = calculateMaxColumnHeight(); | 128 changed = row->recalculateColumnHeight(calculationMode) || changed; |
244 | 129 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 } | 130 } |
274 | 131 |
275 void RenderMultiColumnSet::recordSpaceShortage(LayoutUnit spaceShortage) | 132 void RenderMultiColumnSet::recordSpaceShortage(LayoutUnit offsetInFlowThread, La youtUnit spaceShortage) |
276 { | 133 { |
277 if (spaceShortage >= m_minSpaceShortage) | 134 columnRowAtFlowThreadOffset(offsetInFlowThread)->recordSpaceShortage(spaceSh ortage); |
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 } | 135 } |
286 | 136 |
287 void RenderMultiColumnSet::resetColumnHeight() | 137 void RenderMultiColumnSet::resetColumnHeight() |
288 { | 138 { |
289 // Nuke previously stored minimum column height. Contents may have changed f or all we know. | 139 // FIXME: don't we really have to just delete all rows but the first one her e? |
290 m_minimumColumnHeight = 0; | 140 for (MultiColumnRow* row = firstColumnRow(); row; row = row->nextRow()) |
141 row->resetColumnHeight(); | |
142 } | |
291 | 143 |
292 m_maxColumnHeight = calculateMaxColumnHeight(); | 144 void RenderMultiColumnSet::beginFlow(LayoutUnit offsetInFlowThread) |
145 { | |
146 // At this point layout is exactly at the beginning of this set. Store block offset from flow | |
147 // thread start. | |
148 firstColumnRow()->setLogicalTopInFlowThread(offsetInFlowThread); | |
149 } | |
293 | 150 |
294 LayoutUnit oldColumnHeight = pageLogicalHeight(); | 151 void RenderMultiColumnSet::endFlow(LayoutUnit offsetInFlowThread) |
295 | 152 { |
296 if (heightIsAuto()) | 153 // At this point layout is exactly at the end of this set. Store block offse t from flow thread |
297 m_columnHeight = 0; | 154 // start. This set is now considered "flowed", although we may have to revis it it later (with |
298 else | 155 // 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())); | 156 // initial margin collapsing estimates were wrong. |
300 | 157 lastColumnRow()->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 } | 158 } |
309 | 159 |
310 void RenderMultiColumnSet::expandToEncompassFlowThreadContentsIfNeeded() | 160 void RenderMultiColumnSet::expandToEncompassFlowThreadContentsIfNeeded() |
311 { | 161 { |
312 ASSERT(multiColumnFlowThread()->lastMultiColumnSet() == this); | 162 ASSERT(multiColumnFlowThread()->lastMultiColumnSet() == this); |
313 LayoutRect rect(flowThreadPortionRect()); | 163 // FIXME: this may result in the need for creating additional rows, since th ere may not be |
314 | 164 // enough space remaining in the currently last row. |
315 // Get the offset within the flow thread in its block progression direction. Then get the | 165 lastColumnRow()->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 } | 166 } |
326 | 167 |
327 void RenderMultiColumnSet::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTo p, LogicalExtentComputedValues& computedValues) const | 168 void RenderMultiColumnSet::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTo p, LogicalExtentComputedValues& computedValues) const |
328 { | 169 { |
329 computedValues.m_extent = m_columnHeight; | 170 LayoutUnit logicalHeight; |
171 for (const MultiColumnRow* row = firstColumnRow(); row; row = row->nextRow() ) | |
172 logicalHeight += row->logicalHeight(); | |
173 computedValues.m_extent = logicalHeight; | |
330 computedValues.m_position = logicalTop; | 174 computedValues.m_position = logicalTop; |
331 } | 175 } |
332 | 176 |
333 LayoutUnit RenderMultiColumnSet::calculateMaxColumnHeight() const | |
334 { | |
335 RenderBlockFlow* multicolBlock = multiColumnBlockFlow(); | |
336 RenderStyle* multicolStyle = multicolBlock->style(); | |
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 | 177 LayoutUnit RenderMultiColumnSet::columnGap() const |
348 { | 178 { |
349 RenderBlockFlow* parentBlock = multiColumnBlockFlow(); | 179 RenderBlockFlow* parentBlock = multiColumnBlockFlow(); |
350 if (parentBlock->style()->hasNormalColumnGap()) | 180 if (parentBlock->style()->hasNormalColumnGap()) |
351 return parentBlock->style()->fontDescription().computedPixelSize(); // " 1em" is recommended as the normal gap setting. Matches <p> margins. | 181 return parentBlock->style()->fontDescription().computedPixelSize(); // " 1em" is recommended as the normal gap setting. Matches <p> margins. |
352 return parentBlock->style()->columnGap(); | 182 return parentBlock->style()->columnGap(); |
353 } | 183 } |
354 | 184 |
355 unsigned RenderMultiColumnSet::actualColumnCount() const | 185 unsigned RenderMultiColumnSet::actualColumnCount() const |
356 { | 186 { |
357 // We must always return a value of 1 or greater. Column count = 0 is a mean ingless situation, | 187 // 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. | 188 // you actually have?", since that may vary for each row. |
359 if (!pageLogicalHeight()) | 189 return firstColumnRow()->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 } | 190 } |
462 | 191 |
463 void RenderMultiColumnSet::paintObject(const PaintInfo& paintInfo, const LayoutP oint& paintOffset) | 192 void RenderMultiColumnSet::paintObject(const PaintInfo& paintInfo, const LayoutP oint& paintOffset) |
464 { | 193 { |
465 MultiColumnSetPainter(*this).paintObject(paintInfo, paintOffset); | 194 MultiColumnSetPainter(*this).paintObject(paintInfo, paintOffset); |
466 } | 195 } |
467 | 196 |
468 void RenderMultiColumnSet::collectLayerFragments(LayerFragments& fragments, cons t LayoutRect& layerBoundingBox, const LayoutRect& dirtyRect) | 197 void RenderMultiColumnSet::collectLayerFragments(LayerFragments& fragments, cons t LayoutRect& layerBoundingBox, const LayoutRect& dirtyRect) |
469 { | 198 { |
470 // |layerBoundingBox| is in the flow thread coordinate space, relative to th e top/left edge of | 199 for (MultiColumnRow* row = firstColumnRow(); row; row = row->nextRow()) |
Julien - ping for review
2015/01/29 17:37:47
Could we use C++11 for-loop here? (and probably fo
mstensho (USE GERRIT)
2015/02/02 17:57:24
Done.
I was planning to use a home-made linked li
| |
471 // the flow thread, but note that it has been converted with respect to writ ing mode (so that | 200 row->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 (RenderLayer-style) rectangle. | |
573 flowThread->flipForWritingMode(flippedFlowThreadOverflowPortion); | |
574 fragment.paginationClip = flippedFlowThreadOverflowPortion; | |
575 fragments.append(fragment); | |
576 } | |
577 } | 201 } |
578 | 202 |
579 void RenderMultiColumnSet::addOverflowFromChildren() | 203 void RenderMultiColumnSet::addOverflowFromChildren() |
580 { | 204 { |
581 unsigned colCount = actualColumnCount(); | 205 LayoutRect overflowRect; |
582 if (!colCount) | 206 for (MultiColumnRow* row = firstColumnRow(); row; row = row->nextRow()) { |
583 return; | 207 LayoutRect rect = row->calculateOverflow(); |
208 rect.move(row->offsetFromColumnSet()); | |
209 overflowRect.unite(rect); | |
210 } | |
211 addLayoutOverflow(overflowRect); | |
212 if (!hasOverflowClip()) | |
213 addVisualOverflow(overflowRect); | |
214 } | |
584 | 215 |
585 LayoutRect lastRect = columnRectAt(colCount - 1); | 216 MultiColumnRow* RenderMultiColumnSet::columnRowAtFlowThreadOffset(LayoutUnit) |
586 addLayoutOverflow(lastRect); | 217 { |
587 if (!hasOverflowClip()) | 218 // FIXME: implement this, once we have support for multiple rows. |
588 addVisualOverflow(lastRect); | 219 return &m_columnRow; |
220 } | |
221 | |
222 const MultiColumnRow* RenderMultiColumnSet::columnRowAtFlowThreadOffset(LayoutUn it) const | |
223 { | |
224 // FIXME: implement this, once we have support for multiple rows. | |
225 return &m_columnRow; | |
589 } | 226 } |
590 | 227 |
591 const char* RenderMultiColumnSet::renderName() const | 228 const char* RenderMultiColumnSet::renderName() const |
592 { | 229 { |
593 return "RenderMultiColumnSet"; | 230 return "RenderMultiColumnSet"; |
594 } | 231 } |
595 | 232 |
596 void RenderMultiColumnSet::insertedIntoTree() | 233 void RenderMultiColumnSet::insertedIntoTree() |
597 { | 234 { |
598 RenderRegion::insertedIntoTree(); | 235 RenderRegion::insertedIntoTree(); |
(...skipping 24 matching lines...) Expand all Loading... | |
623 } | 260 } |
624 | 261 |
625 void RenderMultiColumnSet::detachRegion() | 262 void RenderMultiColumnSet::detachRegion() |
626 { | 263 { |
627 if (m_flowThread) { | 264 if (m_flowThread) { |
628 m_flowThread->removeRegionFromThread(this); | 265 m_flowThread->removeRegionFromThread(this); |
629 m_flowThread = 0; | 266 m_flowThread = 0; |
630 } | 267 } |
631 } | 268 } |
632 | 269 |
270 LayoutRect RenderMultiColumnSet::flowThreadPortionRect() const | |
271 { | |
272 LayoutRect portionRect(LayoutUnit(), logicalTopInFlowThread(), pageLogicalWi dth(), logicalHeightInFlowThread()); | |
273 if (!isHorizontalWritingMode()) | |
274 return portionRect.transposedRect(); | |
275 return portionRect; | |
633 } | 276 } |
277 | |
278 } | |
OLD | NEW |