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, 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 
     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     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 
    175 void FixedTableLayout::computeIntrinsicLogicalWidths(LayoutUnit& minWidth, LayoutUnit& maxWidth)
    176 {
    177     minWidth = maxWidth = calcWidthArray();
    178 }
    179 
    180 void 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 
    202 void 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 
    316 void 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