| 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 | |