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
71namespace WebCore {
72
73FixedTableLayout::FixedTableLayout(RenderTable* table)
74 : TableLayout(table)
75{
76}
77
78float FixedTableLayout::calcWidthArray()
79{
80 // FIXME: We might want to wait until we have all of the first row before computing for the first time.
81 float usedWidth = 0;
82
83 // iterate over all <col> elements
84 unsigned nEffCols = m_table->numEffCols();
85 m_width.resize(nEffCols);
86 m_width.fill(Length(Auto));
87
88 unsigned currentEffectiveColumn = 0;
89 for (RenderTableCol* col = m_table->firstColumn(); col; col = col->nextColumn()) {
90 // RenderTableCols don't have the concept of preferred logical width, but we need to clear their dirty bits
91 // so that if we call setPreferredWidthsDirty(true) on a col or one of its descendants, we'll mark it's
92 // ancestors as dirty.
93 col->clearPreferredLogicalWidthsDirtyBits();
94
95 // Width specified by column-groups that have column child does not affect column width in fixed layout tables
96 if (col->isTableColumnGroupWithColumnChildren())
97 continue;
98
99 Length colStyleLogicalWidth = col->style().logicalWidth();
100 float effectiveColWidth = 0;
101 if (colStyleLogicalWidth.isFixed() && colStyleLogicalWidth.value() > 0)
102 effectiveColWidth = colStyleLogicalWidth.value();
103
104 unsigned span = col->span();
105 while (span) {
106 unsigned spanInCurrentEffectiveColumn;
107 if (currentEffectiveColumn >= nEffCols) {
108 m_table->appendColumn(span);
109 nEffCols++;
110 m_width.append(Length());
111 spanInCurrentEffectiveColumn = span;
112 } else {
113 if (span < m_table->spanOfEffCol(currentEffectiveColumn)) {
114 m_table->splitColumn(currentEffectiveColumn, span);
115 nEffCols++;
116 m_width.append(Length());
117 }
118 spanInCurrentEffectiveColumn = m_table->spanOfEffCol(currentEffectiveColumn);
119 }
120 if ((colStyleLogicalWidth.isFixed() || colStyleLogicalWidth.isPercentOrCalculated()) && colStyleLogicalWidth.isPositive()) {
121 m_width[currentEffectiveColumn] = colStyleLogicalWidth;
122 m_width[currentEffectiveColumn] *= spanInCurrentEffectiveColumn;
123 usedWidth += effectiveColWidth * spanInCurrentEffectiveColumn;
124 }
125 span -= spanInCurrentEffectiveColumn;
126 currentEffectiveColumn++;
127 }
128 }
129
130 // Iterate over the first row in case some are unspecified.
131 RenderTableSection* section = m_table->topNonEmptySection();
132 if (!section)
133 return usedWidth;
134
135 unsigned currentColumn = 0;
136
137 RenderTableRow* firstRow = section->firstRow();
138 for (RenderTableCell* cell = firstRow->firstCell(); cell; cell = cell->nextCell()) {
139 Length logicalWidth = cell->styleOrColLogicalWidth();
140 unsigned span = cell->colSpan();
141 float fixedBorderBoxLogicalWidth = 0;
142 // FIXME: Support other length types. If the width is non-auto, it should probably just use
143 // RenderBox::computeLogicalWidthInFragmentUsing to compute the width.
144 if (logicalWidth.isFixed() && logicalWidth.isPositive()) {
145 fixedBorderBoxLogicalWidth = cell->adjustBorderBoxLogicalWidthForBoxSizing(logicalWidth.value());
146 logicalWidth.setValue(Fixed, fixedBorderBoxLogicalWidth);
147 }
148
149 unsigned usedSpan = 0;
150 while (usedSpan < span && currentColumn < nEffCols) {
151 float eSpan = m_table->spanOfEffCol(currentColumn);
152 // Only set if no col element has already set it.
153 if (m_width[currentColumn].isAuto() && logicalWidth.type() != Auto) {
154 m_width[currentColumn] = logicalWidth;
155 m_width[currentColumn] *= eSpan / span;
156 usedWidth += fixedBorderBoxLogicalWidth * eSpan / span;
157 }
158 usedSpan += eSpan;
159 ++currentColumn;
160 }
161
162 // FixedTableLayout doesn't use min/maxPreferredLogicalWidths, but we need to clear the
163 // dirty bit on the cell so that we'll correctly mark its ancestors dirty
164 // in case we later call setPreferredLogicalWidthsDirty(true) on it later.
165 if (cell->preferredLogicalWidthsDirty())
166 cell->setPreferredLogicalWidthsDirty(false);
167 }
168
169 return usedWidth;
170}
171
172void FixedTableLayout::computeIntrinsicLogicalWidths(LayoutUnit& minWidth, LayoutUnit& maxWidth)
173{
174 minWidth = maxWidth = calcWidthArray();
175}
176
177void FixedTableLayout::applyPreferredLogicalWidthQuirks(LayoutUnit& minWidth, LayoutUnit& maxWidth) const
178{
179 Length tableLogicalWidth = m_table->style().logicalWidth();
180 if (tableLogicalWidth.isFixed() && tableLogicalWidth.isPositive())
181 minWidth = maxWidth = std::max(minWidth, LayoutUnit(tableLogicalWidth.value()) - m_table->bordersPaddingAndSpacingInRowDirection());
182
183 /*
184 <table style="width:100%; background-color:red"><tr><td>
185 <table style="background-color:blue"><tr><td>
186 <table style="width:100%; background-color:green; table-layout:fixed"><tr><td>
187 Content
188 </td></tr></table>
189 </td></tr></table>
190 </td></tr></table>
191 */
192 // In this example, the two inner tables should be as large as the outer table.
193 // We can achieve this effect by making the maxwidth of fixed tables with percentage
194 // widths be infinite.
195 if (m_table->style().logicalWidth().isPercentOrCalculated() && maxWidth < tableMaxWidth)
196 maxWidth = tableMaxWidth;
197}
198
199void FixedTableLayout::layout()
200{
201 float tableLogicalWidth = m_table->logicalWidth() - m_table->bordersPaddingAndSpacingInRowDirection();
202 unsigned nEffCols = m_table->numEffCols();
203
204 // FIXME: It is possible to be called without having properly updated our internal representation.
205 // This means that our preferred logical widths were not recomputed as expected.
206 if (nEffCols != m_width.size()) {
207 calcWidthArray();
208 // FIXME: Table layout shouldn't modify our table structure (but does due to columns and column-groups).
209 nEffCols = m_table->numEffCols();
210 }
211
212 Vector<float> calcWidth(nEffCols, 0);
213
214 unsigned numAuto = 0;
215 unsigned autoSpan = 0;
216 float totalFixedWidth = 0;
217 float totalPercentWidth = 0;
218 float totalPercent = 0;
219
220 // Compute requirements and try to satisfy fixed and percent widths.
221 // Percentages are of the table's width, so for example
222 // for a table width of 100px with columns (40px, 10%), the 10% compute
223 // to 10px here, and will scale up to 20px in the final (80px, 20px).
224 for (unsigned i = 0; i < nEffCols; i++) {
225 if (m_width[i].isFixed()) {
226 calcWidth[i] = m_width[i].value();
227 totalFixedWidth += calcWidth[i];
228 } else if (m_width[i].isPercent()) {
229 calcWidth[i] = valueForLength(m_width[i], tableLogicalWidth);
230 totalPercentWidth += calcWidth[i];
231 totalPercent += m_width[i].percent();
232 } else if (m_width[i].isAuto()) {
233 numAuto++;
234 autoSpan += m_table->spanOfEffCol(i);
235 }
236 }
237
238 float hspacing = m_table->hBorderSpacing();
239 float totalWidth = totalFixedWidth + totalPercentWidth;
240 if (!numAuto || totalWidth > tableLogicalWidth) {
241 // If there are no auto columns, or if the total is too wide, take
242 // what we have and scale it to fit as necessary.
243 if (totalWidth != tableLogicalWidth) {
244 // Fixed widths only scale up
245 if (totalFixedWidth && totalWidth < tableLogicalWidth) {
246 totalFixedWidth = 0;
247 for (unsigned i = 0; i < nEffCols; i++) {
248 if (m_width[i].isFixed()) {
249 calcWidth[i] = calcWidth[i] * tableLogicalWidth / totalWidth;
250 totalFixedWidth += calcWidth[i];
251 }
252 }
253 }
254 if (totalPercent) {
255 totalPercentWidth = 0;
256 for (unsigned i = 0; i < nEffCols; i++) {
257 if (m_width[i].isPercent()) {
258 calcWidth[i] = m_width[i].percent() * (tableLogicalWidth - totalFixedWidth) / totalPercent;
259 totalPercentWidth += calcWidth[i];
260 }
261 }
262 }
263 totalWidth = totalFixedWidth + totalPercentWidth;
264 }
265 } else {
266 // Divide the remaining width among the auto columns.
267 ASSERT(autoSpan >= numAuto);
268 float remainingWidth = tableLogicalWidth - totalFixedWidth - totalPercentWidth - hspacing * (autoSpan - numAuto);
269 int lastAuto = 0;
270 for (unsigned i = 0; i < nEffCols; i++) {
271 if (m_width[i].isAuto()) {
272 unsigned span = m_table->spanOfEffCol(i);
273 float w = remainingWidth * span / autoSpan;
274 calcWidth[i] = w + hspacing * (span - 1);
275 remainingWidth -= w;
276 if (!remainingWidth)
277 break;
278 lastAuto = i;
279 numAuto--;
280 ASSERT(autoSpan >= span);
281 autoSpan -= span;
282 }
283 }
284 // Last one gets the remainder.
285 if (remainingWidth)
286 calcWidth[lastAuto] += remainingWidth;
287 totalWidth = tableLogicalWidth;
288 }
289
290 if (totalWidth < tableLogicalWidth) {
291 // Spread extra space over columns.
292 float remainingWidth = tableLogicalWidth - totalWidth;
293 int total = nEffCols;
294 while (total) {
295 float w = remainingWidth / total;
296 remainingWidth -= w;
297 calcWidth[--total] += w;
298 }
299 if (nEffCols > 0)
300 calcWidth[nEffCols - 1] += remainingWidth;
301 }
302
303 float pos = 0;
304 for (unsigned i = 0; i < nEffCols; i++) {
305 m_table->setColumnPosition(i, pos);
306 pos += calcWidth[i] + hspacing;
307 }
308 float colPositionsSize = m_table->columnPositions().size();
309 if (colPositionsSize > 0)
310 m_table->setColumnPosition(colPositionsSize - 1, pos);
311}
312
313} // namespace WebCore
314