1 /* 2 * Copyright (C) 2002 Lars Knoll (knoll (at) kde.org) 3 * (C) 2002 Dirk Mueller (mueller (at) 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 76 FixedTableLayout::FixedTableLayout(RenderTable* table) 77 : TableLayout(table) 78 { 79 } 80 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 180 void FixedTableLayout::computeIntrinsicLogicalWidths(LayoutUnit& minWidth, LayoutUnit& maxWidth) 181 { 182 minWidth = maxWidth = calcWidthArray(); 183 } 184 185 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 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 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