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