1/*
2 * Copyright (C) 2002 Lars Knoll (knoll@kde.org)
3 *           (C) 2002 Dirk Mueller (mueller@kde.org)
4 * Copyright (C) 2003, 2006, 2008, 2010 Apple Inc. All rights reserved.
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/AutoTableLayout.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
30using namespace std;
31
32namespace WebCore {
33
34AutoTableLayout::AutoTableLayout(RenderTable* table)
35    : TableLayout(table)
36    , m_hasPercent(false)
37    , m_effectiveLogicalWidthDirty(true)
38{
39}
40
41AutoTableLayout::~AutoTableLayout()
42{
43}
44
45void AutoTableLayout::recalcColumn(unsigned effCol)
46{
47    Layout& columnLayout = m_layoutStruct[effCol];
48
49    RenderTableCell* fixedContributor = 0;
50    RenderTableCell* maxContributor = 0;
51
52    for (RenderObject* child = m_table->children()->firstChild(); child; child = child->nextSibling()) {
53        if (child->isRenderTableCol()){
54            // RenderTableCols don't have the concept of preferred logical width, but we need to clear their dirty bits
55            // so that if we call setPreferredWidthsDirty(true) on a col or one of its descendants, we'll mark it's
56            // ancestors as dirty.
57            toRenderTableCol(child)->clearPreferredLogicalWidthsDirtyBits();
58        } else if (child->isTableSection()) {
59            RenderTableSection* section = toRenderTableSection(child);
60            unsigned numRows = section->numRows();
61            for (unsigned i = 0; i < numRows; i++) {
62                RenderTableSection::CellStruct current = section->cellAt(i, effCol);
63                RenderTableCell* cell = current.primaryCell();
64
65                if (current.inColSpan || !cell)
66                    continue;
67
68                bool cellHasContent = cell->children()->firstChild() || cell->style()->hasBorder() || cell->style()->hasPadding();
69                if (cellHasContent)
70                    columnLayout.emptyCellsOnly = false;
71
72                // A cell originates in this column. Ensure we have
73                // a min/max width of at least 1px for this column now.
74                columnLayout.minLogicalWidth = max<int>(columnLayout.minLogicalWidth, cellHasContent ? 1 : 0);
75                columnLayout.maxLogicalWidth = max<int>(columnLayout.maxLogicalWidth, 1);
76
77                if (cell->colSpan() == 1) {
78                    columnLayout.minLogicalWidth = max<int>(cell->minPreferredLogicalWidth(), columnLayout.minLogicalWidth);
79                    if (cell->maxPreferredLogicalWidth() > columnLayout.maxLogicalWidth) {
80                        columnLayout.maxLogicalWidth = cell->maxPreferredLogicalWidth();
81                        maxContributor = cell;
82                    }
83
84                    // All browsers implement a size limit on the cell's max width.
85                    // Our limit is based on KHTML's representation that used 16 bits widths.
86                    // FIXME: Other browsers have a lower limit for the cell's max width.
87                    const int cCellMaxWidth = 32760;
88                    Length cellLogicalWidth = cell->styleOrColLogicalWidth();
89                    if (cellLogicalWidth.value() > cCellMaxWidth)
90                        cellLogicalWidth.setValue(cCellMaxWidth);
91                    if (cellLogicalWidth.isNegative())
92                        cellLogicalWidth.setValue(0);
93                    switch (cellLogicalWidth.type()) {
94                    case Fixed:
95                        // ignore width=0
96                        if (cellLogicalWidth.isPositive() && !columnLayout.logicalWidth.isPercent()) {
97                            int logicalWidth = cell->adjustBorderBoxLogicalWidthForBoxSizing(cellLogicalWidth.value());
98                            if (columnLayout.logicalWidth.isFixed()) {
99                                // Nav/IE weirdness
100                                if ((logicalWidth > columnLayout.logicalWidth.value())
101                                    || ((columnLayout.logicalWidth.value() == logicalWidth) && (maxContributor == cell))) {
102                                    columnLayout.logicalWidth.setValue(Fixed, logicalWidth);
103                                    fixedContributor = cell;
104                                }
105                            } else {
106                                columnLayout.logicalWidth.setValue(Fixed, logicalWidth);
107                                fixedContributor = cell;
108                            }
109                        }
110                        break;
111                    case Percent:
112                        m_hasPercent = true;
113                        if (cellLogicalWidth.isPositive() && (!columnLayout.logicalWidth.isPercent() || cellLogicalWidth.value() > columnLayout.logicalWidth.value()))
114                            columnLayout.logicalWidth = cellLogicalWidth;
115                        break;
116                    default:
117                        break;
118                    }
119                } else if (!effCol || section->primaryCellAt(i, effCol - 1) != cell) {
120                    // This spanning cell originates in this column. Insert the cell into spanning cells list.
121                    insertSpanCell(cell);
122                }
123            }
124        }
125    }
126
127    // Nav/IE weirdness
128    if (columnLayout.logicalWidth.isFixed()) {
129        if (m_table->document().inQuirksMode() && columnLayout.maxLogicalWidth > columnLayout.logicalWidth.value() && fixedContributor != maxContributor) {
130            columnLayout.logicalWidth = Length();
131            fixedContributor = 0;
132        }
133    }
134
135    columnLayout.maxLogicalWidth = max(columnLayout.maxLogicalWidth, columnLayout.minLogicalWidth);
136}
137
138void AutoTableLayout::fullRecalc()
139{
140    m_hasPercent = false;
141    m_effectiveLogicalWidthDirty = true;
142
143    unsigned nEffCols = m_table->numEffCols();
144    m_layoutStruct.resize(nEffCols);
145    m_layoutStruct.fill(Layout());
146    m_spanCells.fill(0);
147
148    Length groupLogicalWidth;
149    unsigned currentColumn = 0;
150    for (RenderTableCol* column = m_table->firstColumn(); column; column = column->nextColumn()) {
151        if (column->isTableColumnGroupWithColumnChildren())
152            groupLogicalWidth = column->style()->logicalWidth();
153        else {
154            Length colLogicalWidth = column->style()->logicalWidth();
155            if (colLogicalWidth.isAuto())
156                colLogicalWidth = groupLogicalWidth;
157            if ((colLogicalWidth.isFixed() || colLogicalWidth.isPercent()) && colLogicalWidth.isZero())
158                colLogicalWidth = Length();
159            unsigned effCol = m_table->colToEffCol(currentColumn);
160            unsigned span = column->span();
161            if (!colLogicalWidth.isAuto() && span == 1 && effCol < nEffCols && m_table->spanOfEffCol(effCol) == 1) {
162                m_layoutStruct[effCol].logicalWidth = colLogicalWidth;
163                if (colLogicalWidth.isFixed() && m_layoutStruct[effCol].maxLogicalWidth < colLogicalWidth.value())
164                    m_layoutStruct[effCol].maxLogicalWidth = colLogicalWidth.value();
165            }
166            currentColumn += span;
167        }
168
169        // For the last column in a column-group, we invalidate our group logical width.
170        if (column->isTableColumn() && !column->nextSibling())
171            groupLogicalWidth = Length();
172    }
173
174    for (unsigned i = 0; i < nEffCols; i++)
175        recalcColumn(i);
176}
177
178// FIXME: This needs to be adapted for vertical writing modes.
179static bool shouldScaleColumns(RenderTable* table)
180{
181    // A special case.  If this table is not fixed width and contained inside
182    // a cell, then don't bloat the maxwidth by examining percentage growth.
183    bool scale = true;
184    while (table) {
185        Length tw = table->style()->width();
186        if ((tw.isAuto() || tw.isPercent()) && !table->isOutOfFlowPositioned()) {
187            RenderBlock* cb = table->containingBlock();
188            while (cb && !cb->isRenderView() && !cb->isTableCell() &&
189                cb->style()->width().isAuto() && !cb->isOutOfFlowPositioned())
190                cb = cb->containingBlock();
191
192            table = 0;
193            if (cb && cb->isTableCell() &&
194                (cb->style()->width().isAuto() || cb->style()->width().isPercent())) {
195                RenderTableCell* cell = toRenderTableCell(cb);
196                if (cell->colSpan() > 1 || cell->table()->style()->width().isAuto())
197                    scale = false;
198                else
199                    table = cell->table();
200            }
201        }
202        else
203            table = 0;
204    }
205    return scale;
206}
207
208void AutoTableLayout::computeIntrinsicLogicalWidths(LayoutUnit& minWidth, LayoutUnit& maxWidth)
209{
210    fullRecalc();
211
212    int spanMaxLogicalWidth = calcEffectiveLogicalWidth();
213    minWidth = 0;
214    maxWidth = 0;
215    float maxPercent = 0;
216    float maxNonPercent = 0;
217    bool scaleColumns = shouldScaleColumns(m_table);
218
219    // We substitute 0 percent by (epsilon / percentScaleFactor) percent in two places below to avoid division by zero.
220    // FIXME: Handle the 0% cases properly.
221    const float epsilon = 1 / 128.0f;
222
223    float remainingPercent = 100;
224    for (size_t i = 0; i < m_layoutStruct.size(); ++i) {
225        minWidth += m_layoutStruct[i].effectiveMinLogicalWidth;
226        maxWidth += m_layoutStruct[i].effectiveMaxLogicalWidth;
227        if (scaleColumns) {
228            if (m_layoutStruct[i].effectiveLogicalWidth.isPercent()) {
229                float percent = min(static_cast<float>(m_layoutStruct[i].effectiveLogicalWidth.percent()), remainingPercent);
230                float logicalWidth = static_cast<float>(m_layoutStruct[i].effectiveMaxLogicalWidth) * 100 / max(percent, epsilon);
231                maxPercent = max(logicalWidth,  maxPercent);
232                remainingPercent -= percent;
233            } else
234                maxNonPercent += m_layoutStruct[i].effectiveMaxLogicalWidth;
235        }
236    }
237
238    if (scaleColumns) {
239        maxNonPercent = maxNonPercent * 100 / max(remainingPercent, epsilon);
240        maxWidth = max<int>(maxWidth, static_cast<int>(min(maxNonPercent, static_cast<float>(tableMaxWidth))));
241        maxWidth = max<int>(maxWidth, static_cast<int>(min(maxPercent, static_cast<float>(tableMaxWidth))));
242    }
243
244    maxWidth = max<int>(maxWidth, spanMaxLogicalWidth);
245}
246
247void AutoTableLayout::applyPreferredLogicalWidthQuirks(LayoutUnit& minWidth, LayoutUnit& maxWidth) const
248{
249    Length tableLogicalWidth = m_table->style()->logicalWidth();
250    if (tableLogicalWidth.isFixed() && tableLogicalWidth.isPositive())
251        minWidth = maxWidth = max<int>(minWidth, tableLogicalWidth.value());
252}
253
254/*
255  This method takes care of colspans.
256  effWidth is the same as width for cells without colspans. If we have colspans, they get modified.
257 */
258int AutoTableLayout::calcEffectiveLogicalWidth()
259{
260    int maxLogicalWidth = 0;
261
262    size_t nEffCols = m_layoutStruct.size();
263    int spacingInRowDirection = m_table->hBorderSpacing();
264
265    for (size_t i = 0; i < nEffCols; ++i) {
266        m_layoutStruct[i].effectiveLogicalWidth = m_layoutStruct[i].logicalWidth;
267        m_layoutStruct[i].effectiveMinLogicalWidth = m_layoutStruct[i].minLogicalWidth;
268        m_layoutStruct[i].effectiveMaxLogicalWidth = m_layoutStruct[i].maxLogicalWidth;
269    }
270
271    for (size_t i = 0; i < m_spanCells.size(); ++i) {
272        RenderTableCell* cell = m_spanCells[i];
273        if (!cell)
274            break;
275
276        unsigned span = cell->colSpan();
277
278        Length cellLogicalWidth = cell->styleOrColLogicalWidth();
279        if (cellLogicalWidth.isZero())
280            cellLogicalWidth = Length(); // make it Auto
281
282        unsigned effCol = m_table->colToEffCol(cell->col());
283        size_t lastCol = effCol;
284        int cellMinLogicalWidth = cell->minPreferredLogicalWidth() + spacingInRowDirection;
285        int cellMaxLogicalWidth = cell->maxPreferredLogicalWidth() + spacingInRowDirection;
286        float totalPercent = 0;
287        int spanMinLogicalWidth = 0;
288        int spanMaxLogicalWidth = 0;
289        bool allColsArePercent = true;
290        bool allColsAreFixed = true;
291        bool haveAuto = false;
292        bool spanHasEmptyCellsOnly = true;
293        int fixedWidth = 0;
294        while (lastCol < nEffCols && span > 0) {
295            Layout& columnLayout = m_layoutStruct[lastCol];
296            switch (columnLayout.logicalWidth.type()) {
297            case Percent:
298                totalPercent += columnLayout.logicalWidth.percent();
299                allColsAreFixed = false;
300                break;
301            case Fixed:
302                if (columnLayout.logicalWidth.value() > 0) {
303                    fixedWidth += columnLayout.logicalWidth.value();
304                    allColsArePercent = false;
305                    // IE resets effWidth to Auto here, but this breaks the konqueror about page and seems to be some bad
306                    // legacy behaviour anyway. mozilla doesn't do this so I decided we don't neither.
307                    break;
308                }
309                // fall through
310            case Auto:
311                haveAuto = true;
312                // fall through
313            default:
314                // If the column is a percentage width, do not let the spanning cell overwrite the
315                // width value.  This caused a mis-rendering on amazon.com.
316                // Sample snippet:
317                // <table border=2 width=100%><
318                //   <tr><td>1</td><td colspan=2>2-3</tr>
319                //   <tr><td>1</td><td colspan=2 width=100%>2-3</td></tr>
320                // </table>
321                if (!columnLayout.effectiveLogicalWidth.isPercent()) {
322                    columnLayout.effectiveLogicalWidth = Length();
323                    allColsArePercent = false;
324                } else
325                    totalPercent += columnLayout.effectiveLogicalWidth.percent();
326                allColsAreFixed = false;
327            }
328            if (!columnLayout.emptyCellsOnly)
329                spanHasEmptyCellsOnly = false;
330            span -= m_table->spanOfEffCol(lastCol);
331            spanMinLogicalWidth += columnLayout.effectiveMinLogicalWidth;
332            spanMaxLogicalWidth += columnLayout.effectiveMaxLogicalWidth;
333            lastCol++;
334            cellMinLogicalWidth -= spacingInRowDirection;
335            cellMaxLogicalWidth -= spacingInRowDirection;
336        }
337
338        // adjust table max width if needed
339        if (cellLogicalWidth.isPercent()) {
340            if (totalPercent > cellLogicalWidth.percent() || allColsArePercent) {
341                // can't satify this condition, treat as variable
342                cellLogicalWidth = Length();
343            } else {
344                maxLogicalWidth = max(maxLogicalWidth, static_cast<int>(max(spanMaxLogicalWidth, cellMaxLogicalWidth) * 100  / cellLogicalWidth.percent()));
345
346                // all non percent columns in the span get percent values to sum up correctly.
347                float percentMissing = cellLogicalWidth.percent() - totalPercent;
348                int totalWidth = 0;
349                for (unsigned pos = effCol; pos < lastCol; ++pos) {
350                    if (!m_layoutStruct[pos].effectiveLogicalWidth.isPercent())
351                        totalWidth += m_layoutStruct[pos].effectiveMaxLogicalWidth;
352                }
353
354                for (unsigned pos = effCol; pos < lastCol && totalWidth > 0; ++pos) {
355                    if (!m_layoutStruct[pos].effectiveLogicalWidth.isPercent()) {
356                        float percent = percentMissing * static_cast<float>(m_layoutStruct[pos].effectiveMaxLogicalWidth) / totalWidth;
357                        totalWidth -= m_layoutStruct[pos].effectiveMaxLogicalWidth;
358                        percentMissing -= percent;
359                        if (percent > 0)
360                            m_layoutStruct[pos].effectiveLogicalWidth.setValue(Percent, percent);
361                        else
362                            m_layoutStruct[pos].effectiveLogicalWidth = Length();
363                    }
364                }
365            }
366        }
367
368        // make sure minWidth and maxWidth of the spanning cell are honoured
369        if (cellMinLogicalWidth > spanMinLogicalWidth) {
370            if (allColsAreFixed) {
371                for (unsigned pos = effCol; fixedWidth > 0 && pos < lastCol; ++pos) {
372                    int cellLogicalWidth = max(m_layoutStruct[pos].effectiveMinLogicalWidth, static_cast<int>(cellMinLogicalWidth * m_layoutStruct[pos].logicalWidth.value() / fixedWidth));
373                    fixedWidth -= m_layoutStruct[pos].logicalWidth.value();
374                    cellMinLogicalWidth -= cellLogicalWidth;
375                    m_layoutStruct[pos].effectiveMinLogicalWidth = cellLogicalWidth;
376                }
377            } else if (allColsArePercent) {
378                // In this case, we just split the colspan's min amd max widths following the percentage.
379                int allocatedMinLogicalWidth = 0;
380                int allocatedMaxLogicalWidth = 0;
381                for (unsigned pos = effCol; pos < lastCol; ++pos) {
382                    ASSERT(m_layoutStruct[pos].logicalWidth.isPercent() || m_layoutStruct[pos].effectiveLogicalWidth.isPercent());
383                    // |allColsArePercent| means that either the logicalWidth *or* the effectiveLogicalWidth are percents, handle both of them here.
384                    float percent = m_layoutStruct[pos].logicalWidth.isPercent() ? m_layoutStruct[pos].logicalWidth.percent() : m_layoutStruct[pos].effectiveLogicalWidth.percent();
385                    int columnMinLogicalWidth = static_cast<int>(percent * cellMinLogicalWidth / totalPercent);
386                    int columnMaxLogicalWidth = static_cast<int>(percent * cellMaxLogicalWidth / totalPercent);
387                    m_layoutStruct[pos].effectiveMinLogicalWidth = max(m_layoutStruct[pos].effectiveMinLogicalWidth, columnMinLogicalWidth);
388                    m_layoutStruct[pos].effectiveMaxLogicalWidth = columnMaxLogicalWidth;
389                    allocatedMinLogicalWidth += columnMinLogicalWidth;
390                    allocatedMaxLogicalWidth += columnMaxLogicalWidth;
391                }
392                ASSERT(allocatedMinLogicalWidth <= cellMinLogicalWidth);
393                ASSERT(allocatedMaxLogicalWidth <= cellMaxLogicalWidth);
394                cellMinLogicalWidth -= allocatedMinLogicalWidth;
395                cellMaxLogicalWidth -= allocatedMaxLogicalWidth;
396            } else {
397                int remainingMaxLogicalWidth = spanMaxLogicalWidth;
398                int remainingMinLogicalWidth = spanMinLogicalWidth;
399
400                // Give min to variable first, to fixed second, and to others third.
401                for (unsigned pos = effCol; remainingMaxLogicalWidth >= 0 && pos < lastCol; ++pos) {
402                    if (m_layoutStruct[pos].logicalWidth.isFixed() && haveAuto && fixedWidth <= cellMinLogicalWidth) {
403                        int colMinLogicalWidth = max<int>(m_layoutStruct[pos].effectiveMinLogicalWidth, m_layoutStruct[pos].logicalWidth.value());
404                        fixedWidth -= m_layoutStruct[pos].logicalWidth.value();
405                        remainingMinLogicalWidth -= m_layoutStruct[pos].effectiveMinLogicalWidth;
406                        remainingMaxLogicalWidth -= m_layoutStruct[pos].effectiveMaxLogicalWidth;
407                        cellMinLogicalWidth -= colMinLogicalWidth;
408                        m_layoutStruct[pos].effectiveMinLogicalWidth = colMinLogicalWidth;
409                    }
410                }
411
412                for (unsigned pos = effCol; remainingMaxLogicalWidth >= 0 && pos < lastCol && remainingMinLogicalWidth < cellMinLogicalWidth; ++pos) {
413                    if (!(m_layoutStruct[pos].logicalWidth.isFixed() && haveAuto && fixedWidth <= cellMinLogicalWidth)) {
414                        int colMinLogicalWidth = max<int>(m_layoutStruct[pos].effectiveMinLogicalWidth, static_cast<int>(remainingMaxLogicalWidth ? cellMinLogicalWidth * static_cast<float>(m_layoutStruct[pos].effectiveMaxLogicalWidth) / remainingMaxLogicalWidth : cellMinLogicalWidth));
415                        colMinLogicalWidth = min<int>(m_layoutStruct[pos].effectiveMinLogicalWidth + (cellMinLogicalWidth - remainingMinLogicalWidth), colMinLogicalWidth);
416                        remainingMaxLogicalWidth -= m_layoutStruct[pos].effectiveMaxLogicalWidth;
417                        remainingMinLogicalWidth -= m_layoutStruct[pos].effectiveMinLogicalWidth;
418                        cellMinLogicalWidth -= colMinLogicalWidth;
419                        m_layoutStruct[pos].effectiveMinLogicalWidth = colMinLogicalWidth;
420                    }
421                }
422            }
423        }
424        if (!cellLogicalWidth.isPercent()) {
425            if (cellMaxLogicalWidth > spanMaxLogicalWidth) {
426                for (unsigned pos = effCol; spanMaxLogicalWidth >= 0 && pos < lastCol; ++pos) {
427                    int colMaxLogicalWidth = max(m_layoutStruct[pos].effectiveMaxLogicalWidth, static_cast<int>(spanMaxLogicalWidth ? cellMaxLogicalWidth * static_cast<float>(m_layoutStruct[pos].effectiveMaxLogicalWidth) / spanMaxLogicalWidth : cellMaxLogicalWidth));
428                    spanMaxLogicalWidth -= m_layoutStruct[pos].effectiveMaxLogicalWidth;
429                    cellMaxLogicalWidth -= colMaxLogicalWidth;
430                    m_layoutStruct[pos].effectiveMaxLogicalWidth = colMaxLogicalWidth;
431                }
432            }
433        } else {
434            for (unsigned pos = effCol; pos < lastCol; ++pos)
435                m_layoutStruct[pos].maxLogicalWidth = max(m_layoutStruct[pos].maxLogicalWidth, m_layoutStruct[pos].minLogicalWidth);
436        }
437        // treat span ranges consisting of empty cells only as if they had content
438        if (spanHasEmptyCellsOnly) {
439            for (unsigned pos = effCol; pos < lastCol; ++pos)
440                m_layoutStruct[pos].emptyCellsOnly = false;
441        }
442    }
443    m_effectiveLogicalWidthDirty = false;
444
445    return min(maxLogicalWidth, INT_MAX / 2);
446}
447
448/* gets all cells that originate in a column and have a cellspan > 1
449   Sorts them by increasing cellspan
450*/
451void AutoTableLayout::insertSpanCell(RenderTableCell *cell)
452{
453    ASSERT_ARG(cell, cell && cell->colSpan() != 1);
454    if (!cell || cell->colSpan() == 1)
455        return;
456
457    unsigned size = m_spanCells.size();
458    if (!size || m_spanCells[size-1] != 0) {
459        m_spanCells.grow(size + 10);
460        for (unsigned i = 0; i < 10; i++)
461            m_spanCells[size+i] = 0;
462        size += 10;
463    }
464
465    // add them in sort. This is a slow algorithm, and a binary search or a fast sorting after collection would be better
466    unsigned pos = 0;
467    unsigned span = cell->colSpan();
468    while (pos < m_spanCells.size() && m_spanCells[pos] && span > m_spanCells[pos]->colSpan())
469        pos++;
470    memmove(m_spanCells.data()+pos+1, m_spanCells.data()+pos, (size-pos-1)*sizeof(RenderTableCell *));
471    m_spanCells[pos] = cell;
472}
473
474
475void AutoTableLayout::layout()
476{
477    // table layout based on the values collected in the layout structure.
478    int tableLogicalWidth = m_table->logicalWidth() - m_table->bordersPaddingAndSpacingInRowDirection();
479    int available = tableLogicalWidth;
480    size_t nEffCols = m_table->numEffCols();
481
482    // FIXME: It is possible to be called without having properly updated our internal representation.
483    // This means that our preferred logical widths were not recomputed as expected.
484    if (nEffCols != m_layoutStruct.size()) {
485        fullRecalc();
486        // FIXME: Table layout shouldn't modify our table structure (but does due to columns and column-groups).
487        nEffCols = m_table->numEffCols();
488    }
489
490    if (m_effectiveLogicalWidthDirty)
491        calcEffectiveLogicalWidth();
492
493    bool havePercent = false;
494    int numAuto = 0;
495    int numFixed = 0;
496    float totalAuto = 0;
497    float totalFixed = 0;
498    float totalPercent = 0;
499    int allocAuto = 0;
500    unsigned numAutoEmptyCellsOnly = 0;
501
502    // fill up every cell with its minWidth
503    for (size_t i = 0; i < nEffCols; ++i) {
504        int cellLogicalWidth = m_layoutStruct[i].effectiveMinLogicalWidth;
505        m_layoutStruct[i].computedLogicalWidth = cellLogicalWidth;
506        available -= cellLogicalWidth;
507        Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth;
508        switch (logicalWidth.type()) {
509        case Percent:
510            havePercent = true;
511            totalPercent += logicalWidth.percent();
512            break;
513        case Fixed:
514            numFixed++;
515            totalFixed += m_layoutStruct[i].effectiveMaxLogicalWidth;
516            // fall through
517            break;
518        case Auto:
519            if (m_layoutStruct[i].emptyCellsOnly)
520                numAutoEmptyCellsOnly++;
521            else {
522                numAuto++;
523                totalAuto += m_layoutStruct[i].effectiveMaxLogicalWidth;
524                allocAuto += cellLogicalWidth;
525            }
526            break;
527        default:
528            break;
529        }
530    }
531
532    // allocate width to percent cols
533    if (available > 0 && havePercent) {
534        for (size_t i = 0; i < nEffCols; ++i) {
535            Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth;
536            if (logicalWidth.isPercent()) {
537                int cellLogicalWidth = max<int>(m_layoutStruct[i].effectiveMinLogicalWidth, minimumValueForLength(logicalWidth, tableLogicalWidth));
538                available += m_layoutStruct[i].computedLogicalWidth - cellLogicalWidth;
539                m_layoutStruct[i].computedLogicalWidth = cellLogicalWidth;
540            }
541        }
542        if (totalPercent > 100) {
543            // remove overallocated space from the last columns
544            int excess = tableLogicalWidth * (totalPercent - 100) / 100;
545            for (unsigned i = nEffCols; i; ) {
546                --i;
547                if (m_layoutStruct[i].effectiveLogicalWidth.isPercent()) {
548                    int cellLogicalWidth = m_layoutStruct[i].computedLogicalWidth;
549                    int reduction = min(cellLogicalWidth,  excess);
550                    // the lines below might look inconsistent, but that's the way it's handled in mozilla
551                    excess -= reduction;
552                    int newLogicalWidth = max<int>(m_layoutStruct[i].effectiveMinLogicalWidth, cellLogicalWidth - reduction);
553                    available += cellLogicalWidth - newLogicalWidth;
554                    m_layoutStruct[i].computedLogicalWidth = newLogicalWidth;
555                }
556            }
557        }
558    }
559
560    // then allocate width to fixed cols
561    if (available > 0) {
562        for (size_t i = 0; i < nEffCols; ++i) {
563            Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth;
564            if (logicalWidth.isFixed() && logicalWidth.value() > m_layoutStruct[i].computedLogicalWidth) {
565                available += m_layoutStruct[i].computedLogicalWidth - logicalWidth.value();
566                m_layoutStruct[i].computedLogicalWidth = logicalWidth.value();
567            }
568        }
569    }
570
571    // now satisfy variable
572    if (available > 0 && numAuto) {
573        available += allocAuto; // this gets redistributed
574        for (size_t i = 0; i < nEffCols; ++i) {
575            Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth;
576            if (logicalWidth.isAuto() && totalAuto && !m_layoutStruct[i].emptyCellsOnly) {
577                int cellLogicalWidth = max<int>(m_layoutStruct[i].computedLogicalWidth, static_cast<int>(available * static_cast<float>(m_layoutStruct[i].effectiveMaxLogicalWidth) / totalAuto));
578                available -= cellLogicalWidth;
579                totalAuto -= m_layoutStruct[i].effectiveMaxLogicalWidth;
580                m_layoutStruct[i].computedLogicalWidth = cellLogicalWidth;
581            }
582        }
583    }
584
585    // spread over fixed columns
586    if (available > 0 && numFixed) {
587        for (size_t i = 0; i < nEffCols; ++i) {
588            Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth;
589            if (logicalWidth.isFixed()) {
590                int cellLogicalWidth = static_cast<int>(available * static_cast<float>(m_layoutStruct[i].effectiveMaxLogicalWidth) / totalFixed);
591                available -= cellLogicalWidth;
592                totalFixed -= m_layoutStruct[i].effectiveMaxLogicalWidth;
593                m_layoutStruct[i].computedLogicalWidth += cellLogicalWidth;
594            }
595        }
596    }
597
598    // spread over percent colums
599    if (available > 0 && m_hasPercent && totalPercent < 100) {
600        for (size_t i = 0; i < nEffCols; ++i) {
601            Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth;
602            if (logicalWidth.isPercent()) {
603                int cellLogicalWidth = available * logicalWidth.percent() / totalPercent;
604                available -= cellLogicalWidth;
605                totalPercent -= logicalWidth.percent();
606                m_layoutStruct[i].computedLogicalWidth += cellLogicalWidth;
607                if (!available || !totalPercent)
608                    break;
609            }
610        }
611    }
612
613    // spread over the rest
614    if (available > 0 && nEffCols > numAutoEmptyCellsOnly) {
615        unsigned total = nEffCols - numAutoEmptyCellsOnly;
616        // still have some width to spread
617        for (unsigned i = nEffCols; i; ) {
618            --i;
619            // variable columns with empty cells only don't get any width
620            if (m_layoutStruct[i].effectiveLogicalWidth.isAuto() && m_layoutStruct[i].emptyCellsOnly)
621                continue;
622            int cellLogicalWidth = available / total;
623            available -= cellLogicalWidth;
624            total--;
625            m_layoutStruct[i].computedLogicalWidth += cellLogicalWidth;
626        }
627    }
628
629    // If we have overallocated, reduce every cell according to the difference between desired width and minwidth
630    // this seems to produce to the pixel exact results with IE. Wonder is some of this also holds for width distributing.
631    if (available < 0) {
632        // Need to reduce cells with the following prioritization:
633        // (1) Auto
634        // (2) Fixed
635        // (3) Percent
636        // This is basically the reverse of how we grew the cells.
637        if (available < 0) {
638            int logicalWidthBeyondMin = 0;
639            for (unsigned i = nEffCols; i; ) {
640                --i;
641                Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth;
642                if (logicalWidth.isAuto())
643                    logicalWidthBeyondMin += m_layoutStruct[i].computedLogicalWidth - m_layoutStruct[i].effectiveMinLogicalWidth;
644            }
645
646            for (unsigned i = nEffCols; i && logicalWidthBeyondMin > 0; ) {
647                --i;
648                Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth;
649                if (logicalWidth.isAuto()) {
650                    int minMaxDiff = m_layoutStruct[i].computedLogicalWidth - m_layoutStruct[i].effectiveMinLogicalWidth;
651                    int reduce = available * minMaxDiff / logicalWidthBeyondMin;
652                    m_layoutStruct[i].computedLogicalWidth += reduce;
653                    available -= reduce;
654                    logicalWidthBeyondMin -= minMaxDiff;
655                    if (available >= 0)
656                        break;
657                }
658            }
659        }
660
661        if (available < 0) {
662            int logicalWidthBeyondMin = 0;
663            for (unsigned i = nEffCols; i; ) {
664                --i;
665                Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth;
666                if (logicalWidth.isFixed())
667                    logicalWidthBeyondMin += m_layoutStruct[i].computedLogicalWidth - m_layoutStruct[i].effectiveMinLogicalWidth;
668            }
669
670            for (unsigned i = nEffCols; i && logicalWidthBeyondMin > 0; ) {
671                --i;
672                Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth;
673                if (logicalWidth.isFixed()) {
674                    int minMaxDiff = m_layoutStruct[i].computedLogicalWidth - m_layoutStruct[i].effectiveMinLogicalWidth;
675                    int reduce = available * minMaxDiff / logicalWidthBeyondMin;
676                    m_layoutStruct[i].computedLogicalWidth += reduce;
677                    available -= reduce;
678                    logicalWidthBeyondMin -= minMaxDiff;
679                    if (available >= 0)
680                        break;
681                }
682            }
683        }
684
685        if (available < 0) {
686            int logicalWidthBeyondMin = 0;
687            for (unsigned i = nEffCols; i; ) {
688                --i;
689                Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth;
690                if (logicalWidth.isPercent())
691                    logicalWidthBeyondMin += m_layoutStruct[i].computedLogicalWidth - m_layoutStruct[i].effectiveMinLogicalWidth;
692            }
693
694            for (unsigned i = nEffCols; i && logicalWidthBeyondMin > 0; ) {
695                --i;
696                Length& logicalWidth = m_layoutStruct[i].effectiveLogicalWidth;
697                if (logicalWidth.isPercent()) {
698                    int minMaxDiff = m_layoutStruct[i].computedLogicalWidth - m_layoutStruct[i].effectiveMinLogicalWidth;
699                    int reduce = available * minMaxDiff / logicalWidthBeyondMin;
700                    m_layoutStruct[i].computedLogicalWidth += reduce;
701                    available -= reduce;
702                    logicalWidthBeyondMin -= minMaxDiff;
703                    if (available >= 0)
704                        break;
705                }
706            }
707        }
708    }
709
710    int pos = 0;
711    for (size_t i = 0; i < nEffCols; ++i) {
712        m_table->setColumnPosition(i, pos);
713        pos += m_layoutStruct[i].computedLogicalWidth + m_table->hBorderSpacing();
714    }
715    m_table->setColumnPosition(m_table->columnPositions().size() - 1, pos);
716}
717
718}
719