CSS 2.1 failure: fixed-table-layout-006 fails
[WebKit-https.git] / Source / WebCore / rendering / FixedTableLayout.cpp
1 /*
2  * Copyright (C) 2002 Lars Knoll (knoll@kde.org)
3  *           (C) 2002 Dirk Mueller (mueller@kde.org)
4  * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public License
17  * along with this library; see the file COPYING.LIB.  If not, write to
18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 #include "config.h"
23 #include "FixedTableLayout.h"
24
25 #include "RenderTable.h"
26 #include "RenderTableCell.h"
27 #include "RenderTableCol.h"
28 #include "RenderTableSection.h"
29
30 /*
31   The text below is from the CSS 2.1 specs.
32
33   Fixed table layout
34
35   With this (fast) algorithm, the horizontal layout of the table does
36   not depend on the contents of the cells; it only depends on the
37   table's width, the width of the columns, and borders or cell
38   spacing.
39
40   The table's width may be specified explicitly with the 'width'
41   property. A value of 'auto' (for both 'display: table' and 'display:
42   inline-table') means use the automatic table layout algorithm.
43
44   In the fixed table layout algorithm, the width of each column is
45   determined as follows:
46
47     1. A column element with a value other than 'auto' for the 'width'
48     property sets the width for that column.
49
50     2. Otherwise, a cell in the first row with a value other than
51     'auto' for the 'width' property sets the width for that column. If
52     the cell spans more than one column, the width is divided over the
53     columns.
54
55     3. Any remaining columns equally divide the remaining horizontal
56     table space (minus borders or cell spacing).
57
58   The width of the table is then the greater of the value of the
59   'width' property for the table element and the sum of the column
60   widths (plus cell spacing or borders). If the table is wider than
61   the columns, the extra space should be distributed over the columns.
62
63
64   In this manner, the user agent can begin to lay out the table once
65   the entire first row has been received. Cells in subsequent rows do
66   not affect column widths. Any cell that has content that overflows
67   uses the 'overflow' property to determine whether to clip the
68   overflow content.
69 */
70
71 using namespace std;
72
73 namespace WebCore {
74
75 FixedTableLayout::FixedTableLayout(RenderTable* table)
76     : TableLayout(table)
77 {
78 }
79
80 static RenderObject* nextCol(RenderObject* child)
81 {
82     // If child is a colgroup, the next col is the colgroup's first child col.
83     if (RenderObject* next = child->firstChild())
84         return next;
85     // Otherwise it's the next col along.
86     if (RenderObject* next = child->nextSibling())
87         return next;
88     // Failing that, the child is the last col in a colgroup, so the next col is the next col/colgroup after its colgroup.
89     if (child->parent()->isTableCol())
90         return child->parent()->nextSibling();
91     return 0;
92 }
93
94 int FixedTableLayout::calcWidthArray(int)
95 {
96     int usedWidth = 0;
97
98     // iterate over all <col> elements
99     unsigned nEffCols = m_table->numEffCols();
100     m_width.resize(nEffCols);
101     m_width.fill(Length(Auto));
102
103     unsigned currentEffectiveColumn = 0;
104     for (RenderObject* child = m_table->firstChild();child && child->isTableCol(); child = nextCol(child)) {
105
106         // Width specified by column-groups does not affect column width in fixed layout tables
107         RenderTableCol* col = toRenderTableCol(child);
108         col->computePreferredLogicalWidths();
109
110         if (col->isTableColGroup())
111             continue;
112
113         Length colStyleLogicalWidth = col->style()->logicalWidth();
114         int effectiveColWidth = 0;
115         if (colStyleLogicalWidth.isFixed() && colStyleLogicalWidth.value() > 0)
116             effectiveColWidth = colStyleLogicalWidth.value();
117
118         unsigned span = col->span();
119         while (span) {
120             unsigned spanInCurrentEffectiveColumn;
121             if (currentEffectiveColumn >= nEffCols) {
122                 m_table->appendColumn(span);
123                 nEffCols++;
124                 m_width.append(Length());
125                 spanInCurrentEffectiveColumn = span;
126             } else {
127                 if (span < m_table->spanOfEffCol(currentEffectiveColumn)) {
128                     m_table->splitColumn(currentEffectiveColumn, span);
129                     nEffCols++;
130                     m_width.append(Length());
131                 }
132                 spanInCurrentEffectiveColumn = m_table->spanOfEffCol(currentEffectiveColumn);
133             }
134             if ((colStyleLogicalWidth.isFixed() || colStyleLogicalWidth.isPercent()) && colStyleLogicalWidth.isPositive()) {
135                 m_width[currentEffectiveColumn] = colStyleLogicalWidth;
136                 m_width[currentEffectiveColumn] *= spanInCurrentEffectiveColumn;
137                 usedWidth += effectiveColWidth * spanInCurrentEffectiveColumn;
138             }
139             span -= spanInCurrentEffectiveColumn;
140             currentEffectiveColumn++;
141         }
142     }
143
144     // Iterate over the first row in case some are unspecified.
145     RenderTableSection* section = m_table->topNonEmptySection();
146     if (section) {
147         unsigned cCol = 0;
148         RenderObject* firstRow = section->firstChild();
149         RenderObject* child = firstRow->firstChild();
150         while (child) {
151             if (child->isTableCell()) {
152                 RenderTableCell* cell = toRenderTableCell(child);
153                 if (cell->preferredLogicalWidthsDirty())
154                     cell->computePreferredLogicalWidths();
155
156                 Length w = cell->styleOrColLogicalWidth();
157                 unsigned span = cell->colSpan();
158                 int effectiveColWidth = 0;
159                 if (w.isFixed() && w.isPositive()) {
160                     w.setValue(w.value() + cell->borderAndPaddingLogicalWidth());
161                     effectiveColWidth = w.value();
162                 }
163                 
164                 unsigned usedSpan = 0;
165                 unsigned i = 0;
166                 while (usedSpan < span && cCol + i < nEffCols) {
167                     float eSpan = m_table->spanOfEffCol(cCol + i);
168                     // Only set if no col element has already set it.
169                     if (m_width[cCol + i].isAuto() && w.type() != Auto) {
170                         m_width[cCol + i] = w;
171                         m_width[cCol + i] *= eSpan / span;
172                         usedWidth += effectiveColWidth * eSpan / span;
173                     }
174                     usedSpan += eSpan;
175                     i++;
176                 }
177                 cCol += i;
178             }
179             child = child->nextSibling();
180         }
181     }
182
183     return usedWidth;
184 }
185
186 // Use a very large value (in effect infinite). But not too large!
187 // numeric_limits<int>::max() will too easily overflow widths.
188 // Keep this in synch with BLOCK_MAX_WIDTH in RenderBlock.cpp
189 #define TABLE_MAX_WIDTH 15000
190
191 void FixedTableLayout::computePreferredLogicalWidths(LayoutUnit& minWidth, LayoutUnit& maxWidth)
192 {
193     // FIXME: This entire calculation is incorrect for both minwidth and maxwidth.
194     
195     // we might want to wait until we have all of the first row before
196     // layouting for the first time.
197
198     // only need to calculate the minimum width as the sum of the
199     // cols/cells with a fixed width.
200     //
201     // The maximum width is max(minWidth, tableWidth).
202     int bordersPaddingAndSpacing = m_table->bordersPaddingAndSpacingInRowDirection();
203
204     int tableLogicalWidth = m_table->style()->logicalWidth().isFixed() ? m_table->style()->logicalWidth().value() - bordersPaddingAndSpacing : 0;
205     int mw = calcWidthArray(tableLogicalWidth) + bordersPaddingAndSpacing;
206
207     minWidth = max(mw, tableLogicalWidth);
208     maxWidth = minWidth;
209
210     // This quirk is very similar to one that exists in RenderBlock::calcBlockPrefWidths().
211     // Here's the example for this one:
212     /*
213         <table style="width:100%; background-color:red"><tr><td>
214             <table style="background-color:blue"><tr><td>
215                 <table style="width:100%; background-color:green; table-layout:fixed"><tr><td>
216                     Content
217                 </td></tr></table>
218             </td></tr></table>
219         </td></tr></table>
220     */ 
221     // In this example, the two inner tables should be as large as the outer table. 
222     // We can achieve this effect by making the maxwidth of fixed tables with percentage
223     // widths be infinite.
224     if (m_table->document()->inQuirksMode() && m_table->style()->logicalWidth().isPercent() && maxWidth < TABLE_MAX_WIDTH)
225         maxWidth = TABLE_MAX_WIDTH;
226 }
227
228 void FixedTableLayout::layout()
229 {
230     int tableLogicalWidth = m_table->logicalWidth() - m_table->bordersPaddingAndSpacingInRowDirection();
231     unsigned nEffCols = m_table->numEffCols();
232     Vector<int> calcWidth(nEffCols, 0);
233
234     unsigned numAuto = 0;
235     unsigned autoSpan = 0;
236     int totalFixedWidth = 0;
237     int totalPercentWidth = 0;
238     float totalPercent = 0;
239
240     // Compute requirements and try to satisfy fixed and percent widths.
241     // Percentages are of the table's width, so for example
242     // for a table width of 100px with columns (40px, 10%), the 10% compute
243     // to 10px here, and will scale up to 20px in the final (80px, 20px).
244     for (unsigned i = 0; i < nEffCols; i++) {
245         if (m_width[i].isFixed()) {
246             calcWidth[i] = m_width[i].value();
247             totalFixedWidth += calcWidth[i];
248         } else if (m_width[i].isPercent()) {
249             calcWidth[i] = valueForLength(m_width[i], tableLogicalWidth);
250             totalPercentWidth += calcWidth[i];
251             totalPercent += m_width[i].percent();
252         } else if (m_width[i].isAuto()) {
253             numAuto++;
254             autoSpan += m_table->spanOfEffCol(i);
255         }
256     }
257
258     int hspacing = m_table->hBorderSpacing();
259     int totalWidth = totalFixedWidth + totalPercentWidth;
260     if (!numAuto || totalWidth > tableLogicalWidth) {
261         // If there are no auto columns, or if the total is too wide, take
262         // what we have and scale it to fit as necessary.
263         if (totalWidth != tableLogicalWidth) {
264             // Fixed widths only scale up
265             if (totalFixedWidth && totalWidth < tableLogicalWidth) {
266                 totalFixedWidth = 0;
267                 for (unsigned i = 0; i < nEffCols; i++) {
268                     if (m_width[i].isFixed()) {
269                         calcWidth[i] = calcWidth[i] * tableLogicalWidth / totalWidth;
270                         totalFixedWidth += calcWidth[i];
271                     }
272                 }
273             }
274             if (totalPercent) {
275                 totalPercentWidth = 0;
276                 for (unsigned i = 0; i < nEffCols; i++) {
277                     if (m_width[i].isPercent()) {
278                         calcWidth[i] = m_width[i].percent() * (tableLogicalWidth - totalFixedWidth) / totalPercent;
279                         totalPercentWidth += calcWidth[i];
280                     }
281                 }
282             }
283             totalWidth = totalFixedWidth + totalPercentWidth;
284         }
285     } else {
286         // Divide the remaining width among the auto columns.
287         ASSERT(autoSpan >= numAuto);
288         int remainingWidth = tableLogicalWidth - totalFixedWidth - totalPercentWidth - hspacing * (autoSpan - numAuto);
289         int lastAuto = 0;
290         for (unsigned i = 0; i < nEffCols; i++) {
291             if (m_width[i].isAuto()) {
292                 unsigned span = m_table->spanOfEffCol(i);
293                 int w = remainingWidth * span / autoSpan;
294                 calcWidth[i] = w + hspacing * (span - 1);
295                 remainingWidth -= w;
296                 if (!remainingWidth)
297                     break;
298                 lastAuto = i;
299                 numAuto--;
300                 ASSERT(autoSpan >= span);
301                 autoSpan -= span;
302             }
303         }
304         // Last one gets the remainder.
305         if (remainingWidth)
306             calcWidth[lastAuto] += remainingWidth;
307         totalWidth = tableLogicalWidth;
308     }
309
310     if (totalWidth < tableLogicalWidth) {
311         // Spread extra space over columns.
312         int remainingWidth = tableLogicalWidth - totalWidth;
313         int total = nEffCols;
314         while (total) {
315             int w = remainingWidth / total;
316             remainingWidth -= w;
317             calcWidth[--total] += w;
318         }
319         if (nEffCols > 0)
320             calcWidth[nEffCols - 1] += remainingWidth;
321     }
322     
323     int pos = 0;
324     for (unsigned i = 0; i < nEffCols; i++) {
325         m_table->columnPositions()[i] = pos;
326         pos += calcWidth[i] + hspacing;
327     }
328     int colPositionsSize = m_table->columnPositions().size();
329     if (colPositionsSize > 0)
330         m_table->columnPositions()[colPositionsSize - 1] = pos;
331 }
332
333 } // namespace WebCore