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 "core/rendering/FixedTableLayout.h"
24
25 #include "core/rendering/RenderTable.h"
26 #include "core/rendering/RenderTableCell.h"
27 #include "core/rendering/RenderTableCol.h"
28 #include "core/rendering/RenderTableSection.h"
29 #include "platform/LayoutUnit.h"
30
31 /*
32 The text below is from the CSS 2.1 specs.
33
34 Fixed table layout
35
36 With this (fast) algorithm, the horizontal layout of the table does
37 not depend on the contents of the cells; it only depends on the
38 table's width, the width of the columns, and borders or cell
39 spacing.
40
41 The table's width may be specified explicitly with the 'width'
42 property. A value of 'auto' (for both 'display: table' and 'display:
43 inline-table') means use the automatic table layout algorithm.
44
45 In the fixed table layout algorithm, the width of each column is
46 determined as follows:
47
48 1. A column element with a value other than 'auto' for the 'width'
49 property sets the width for that column.
50
51 2. Otherwise, a cell in the first row with a value other than
52 'auto' for the 'width' property sets the width for that column. If
53 the cell spans more than one column, the width is divided over the
54 columns.
55
56 3. Any remaining columns equally divide the remaining horizontal
57 table space (minus borders or cell spacing).
58
59 The width of the table is then the greater of the value of the
60 'width' property for the table element and the sum of the column
61 widths (plus cell spacing or borders). If the table is wider than
62 the columns, the extra space should be distributed over the columns.
63
64
65 In this manner, the user agent can begin to lay out the table once
66 the entire first row has been received. Cells in subsequent rows do
67 not affect column widths. Any cell that has content that overflows
68 uses the 'overflow' property to determine whether to clip the
69 overflow content.
70 */
71
72 using namespace std;
73
74 namespace WebCore {
75
FixedTableLayout(RenderTable * table)76 FixedTableLayout::FixedTableLayout(RenderTable* table)
77 : TableLayout(table)
78 {
79 }
80
calcWidthArray()81 int FixedTableLayout::calcWidthArray()
82 {
83 // FIXME: We might want to wait until we have all of the first row before computing for the first time.
84 int usedWidth = 0;
85
86 // iterate over all <col> elements
87 unsigned nEffCols = m_table->numEffCols();
88 m_width.resize(nEffCols);
89 m_width.fill(Length(Auto));
90
91 unsigned currentEffectiveColumn = 0;
92 for (RenderTableCol* col = m_table->firstColumn(); col; col = col->nextColumn()) {
93 // RenderTableCols don't have the concept of preferred logical width, but we need to clear their dirty bits
94 // so that if we call setPreferredWidthsDirty(true) on a col or one of its descendants, we'll mark it's
95 // ancestors as dirty.
96 col->clearPreferredLogicalWidthsDirtyBits();
97
98 // Width specified by column-groups that have column child does not affect column width in fixed layout tables
99 if (col->isTableColumnGroupWithColumnChildren())
100 continue;
101
102 Length colStyleLogicalWidth = col->style()->logicalWidth();
103 int effectiveColWidth = 0;
104 if (colStyleLogicalWidth.isFixed() && colStyleLogicalWidth.value() > 0)
105 effectiveColWidth = colStyleLogicalWidth.value();
106
107 unsigned span = col->span();
108 while (span) {
109 unsigned spanInCurrentEffectiveColumn;
110 if (currentEffectiveColumn >= nEffCols) {
111 m_table->appendColumn(span);
112 nEffCols++;
113 m_width.append(Length());
114 spanInCurrentEffectiveColumn = span;
115 } else {
116 if (span < m_table->spanOfEffCol(currentEffectiveColumn)) {
117 m_table->splitColumn(currentEffectiveColumn, span);
118 nEffCols++;
119 m_width.append(Length());
120 }
121 spanInCurrentEffectiveColumn = m_table->spanOfEffCol(currentEffectiveColumn);
122 }
123 if ((colStyleLogicalWidth.isFixed() || colStyleLogicalWidth.isPercent()) && colStyleLogicalWidth.isPositive()) {
124 m_width[currentEffectiveColumn] = colStyleLogicalWidth;
125 m_width[currentEffectiveColumn] *= spanInCurrentEffectiveColumn;
126 usedWidth += effectiveColWidth * spanInCurrentEffectiveColumn;
127 }
128 span -= spanInCurrentEffectiveColumn;
129 currentEffectiveColumn++;
130 }
131 }
132
133 // Iterate over the first row in case some are unspecified.
134 RenderTableSection* section = m_table->topNonEmptySection();
135 if (!section)
136 return usedWidth;
137
138 unsigned currentColumn = 0;
139
140 RenderObject* firstRow = section->firstChild();
141 for (RenderObject* child = firstRow->firstChild(); child; child = child->nextSibling()) {
142 if (!child->isTableCell())
143 continue;
144
145 RenderTableCell* cell = toRenderTableCell(child);
146
147 Length logicalWidth = cell->styleOrColLogicalWidth();
148 unsigned span = cell->colSpan();
149 int fixedBorderBoxLogicalWidth = 0;
150 // FIXME: Support other length types. If the width is non-auto, it should probably just use
151 // RenderBox::computeLogicalWidthInRegionUsing to compute the width.
152 if (logicalWidth.isFixed() && logicalWidth.isPositive()) {
153 fixedBorderBoxLogicalWidth = cell->adjustBorderBoxLogicalWidthForBoxSizing(logicalWidth.value());
154 logicalWidth.setValue(fixedBorderBoxLogicalWidth);
155 }
156
157 unsigned usedSpan = 0;
158 while (usedSpan < span && currentColumn < nEffCols) {
159 float eSpan = m_table->spanOfEffCol(currentColumn);
160 // Only set if no col element has already set it.
161 if (m_width[currentColumn].isAuto() && logicalWidth.type() != Auto) {
162 m_width[currentColumn] = logicalWidth;
163 m_width[currentColumn] *= eSpan / span;
164 usedWidth += fixedBorderBoxLogicalWidth * eSpan / span;
165 }
166 usedSpan += eSpan;
167 ++currentColumn;
168 }
169
170 // FixedTableLayout doesn't use min/maxPreferredLogicalWidths, but we need to clear the
171 // dirty bit on the cell so that we'll correctly mark its ancestors dirty
172 // in case we later call setPreferredLogicalWidthsDirty() on it later.
173 if (cell->preferredLogicalWidthsDirty())
174 cell->clearPreferredLogicalWidthsDirty();
175 }
176
177 return usedWidth;
178 }
179
computeIntrinsicLogicalWidths(LayoutUnit & minWidth,LayoutUnit & maxWidth)180 void FixedTableLayout::computeIntrinsicLogicalWidths(LayoutUnit& minWidth, LayoutUnit& maxWidth)
181 {
182 minWidth = maxWidth = calcWidthArray();
183 }
184
applyPreferredLogicalWidthQuirks(LayoutUnit & minWidth,LayoutUnit & maxWidth) const185 void FixedTableLayout::applyPreferredLogicalWidthQuirks(LayoutUnit& minWidth, LayoutUnit& maxWidth) const
186 {
187 Length tableLogicalWidth = m_table->style()->logicalWidth();
188 if (tableLogicalWidth.isFixed() && tableLogicalWidth.isPositive())
189 minWidth = maxWidth = max<int>(minWidth, tableLogicalWidth.value() - m_table->bordersPaddingAndSpacingInRowDirection());
190
191 /*
192 <table style="width:100%; background-color:red"><tr><td>
193 <table style="background-color:blue"><tr><td>
194 <table style="width:100%; background-color:green; table-layout:fixed"><tr><td>
195 Content
196 </td></tr></table>
197 </td></tr></table>
198 </td></tr></table>
199 */
200 // In this example, the two inner tables should be as large as the outer table.
201 // We can achieve this effect by making the maxwidth of fixed tables with percentage
202 // widths be infinite.
203 if (m_table->style()->logicalWidth().isPercent() && maxWidth < tableMaxWidth)
204 maxWidth = tableMaxWidth;
205 }
206
layout()207 void FixedTableLayout::layout()
208 {
209 int tableLogicalWidth = m_table->logicalWidth() - m_table->bordersPaddingAndSpacingInRowDirection();
210 unsigned nEffCols = m_table->numEffCols();
211
212 // FIXME: It is possible to be called without having properly updated our internal representation.
213 // This means that our preferred logical widths were not recomputed as expected.
214 if (nEffCols != m_width.size()) {
215 calcWidthArray();
216 // FIXME: Table layout shouldn't modify our table structure (but does due to columns and column-groups).
217 nEffCols = m_table->numEffCols();
218 }
219
220 Vector<int> calcWidth(nEffCols, 0);
221
222 unsigned numAuto = 0;
223 unsigned autoSpan = 0;
224 int totalFixedWidth = 0;
225 int totalPercentWidth = 0;
226 float totalPercent = 0;
227
228 // Compute requirements and try to satisfy fixed and percent widths.
229 // Percentages are of the table's width, so for example
230 // for a table width of 100px with columns (40px, 10%), the 10% compute
231 // to 10px here, and will scale up to 20px in the final (80px, 20px).
232 for (unsigned i = 0; i < nEffCols; i++) {
233 if (m_width[i].isFixed()) {
234 calcWidth[i] = m_width[i].value();
235 totalFixedWidth += calcWidth[i];
236 } else if (m_width[i].isPercent()) {
237 calcWidth[i] = valueForLength(m_width[i], tableLogicalWidth);
238 totalPercentWidth += calcWidth[i];
239 totalPercent += m_width[i].percent();
240 } else if (m_width[i].isAuto()) {
241 numAuto++;
242 autoSpan += m_table->spanOfEffCol(i);
243 }
244 }
245
246 int hspacing = m_table->hBorderSpacing();
247 int totalWidth = totalFixedWidth + totalPercentWidth;
248 if (!numAuto || totalWidth > tableLogicalWidth) {
249 // If there are no auto columns, or if the total is too wide, take
250 // what we have and scale it to fit as necessary.
251 if (totalWidth != tableLogicalWidth) {
252 // Fixed widths only scale up
253 if (totalFixedWidth && totalWidth < tableLogicalWidth) {
254 totalFixedWidth = 0;
255 for (unsigned i = 0; i < nEffCols; i++) {
256 if (m_width[i].isFixed()) {
257 calcWidth[i] = calcWidth[i] * tableLogicalWidth / totalWidth;
258 totalFixedWidth += calcWidth[i];
259 }
260 }
261 }
262 if (totalPercent) {
263 totalPercentWidth = 0;
264 for (unsigned i = 0; i < nEffCols; i++) {
265 if (m_width[i].isPercent()) {
266 calcWidth[i] = m_width[i].percent() * (tableLogicalWidth - totalFixedWidth) / totalPercent;
267 totalPercentWidth += calcWidth[i];
268 }
269 }
270 }
271 totalWidth = totalFixedWidth + totalPercentWidth;
272 }
273 } else {
274 // Divide the remaining width among the auto columns.
275 ASSERT(autoSpan >= numAuto);
276 int remainingWidth = tableLogicalWidth - totalFixedWidth - totalPercentWidth - hspacing * (autoSpan - numAuto);
277 int lastAuto = 0;
278 for (unsigned i = 0; i < nEffCols; i++) {
279 if (m_width[i].isAuto()) {
280 unsigned span = m_table->spanOfEffCol(i);
281 int w = remainingWidth * span / autoSpan;
282 calcWidth[i] = w + hspacing * (span - 1);
283 remainingWidth -= w;
284 if (!remainingWidth)
285 break;
286 lastAuto = i;
287 numAuto--;
288 ASSERT(autoSpan >= span);
289 autoSpan -= span;
290 }
291 }
292 // Last one gets the remainder.
293 if (remainingWidth)
294 calcWidth[lastAuto] += remainingWidth;
295 totalWidth = tableLogicalWidth;
296 }
297
298 if (totalWidth < tableLogicalWidth) {
299 // Spread extra space over columns.
300 int remainingWidth = tableLogicalWidth - totalWidth;
301 int total = nEffCols;
302 while (total) {
303 int w = remainingWidth / total;
304 remainingWidth -= w;
305 calcWidth[--total] += w;
306 }
307 if (nEffCols > 0)
308 calcWidth[nEffCols - 1] += remainingWidth;
309 }
310
311 int pos = 0;
312 for (unsigned i = 0; i < nEffCols; i++) {
313 m_table->setColumnPosition(i, pos);
314 pos += calcWidth[i] + hspacing;
315 }
316 int colPositionsSize = m_table->columnPositions().size();
317 if (colPositionsSize > 0)
318 m_table->setColumnPosition(colPositionsSize - 1, pos);
319 }
320
willChangeTableLayout()321 void FixedTableLayout::willChangeTableLayout()
322 {
323 // When switching table layout algorithm, we need to dirty the preferred
324 // logical widths as we cleared the bits without computing them.
325 // (see calcWidthArray above.) This optimization is preferred to always
326 // computing the logical widths we never intended to use.
327 m_table->recalcSectionsIfNeeded();
328 for (RenderTableSection* section = m_table->topNonEmptySection(); section; section = m_table->sectionBelow(section)) {
329 for (unsigned i = 0; i < section->numRows(); i++) {
330 RenderTableRow* row = section->rowRendererAt(i);
331 if (!row)
332 continue;
333 for (RenderObject* cell = row->firstChild(); cell; cell = cell->nextSibling()) {
334 if (!cell->isTableCell())
335 continue;
336 cell->setPreferredLogicalWidthsDirty();
337 }
338 }
339 }
340 }
341
342 } // namespace WebCore
343