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