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 | namespace WebCore { |
72 | |
73 | FixedTableLayout::FixedTableLayout(RenderTable* table) |
74 | : TableLayout(table) |
75 | { |
76 | } |
77 | |
78 | float 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 | |
172 | void FixedTableLayout::computeIntrinsicLogicalWidths(LayoutUnit& minWidth, LayoutUnit& maxWidth) |
173 | { |
174 | minWidth = maxWidth = calcWidthArray(); |
175 | } |
176 | |
177 | void 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 | |
199 | void 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 | |