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