Home | History | Annotate | Download | only in rendering
      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