|  | /* | 
|  | * Copyright (C) 2002 Lars Knoll (knoll@kde.org) | 
|  | *           (C) 2002 Dirk Mueller (mueller@kde.org) | 
|  | * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. | 
|  | * | 
|  | * This library is free software; you can redistribute it and/or | 
|  | * modify it under the terms of the GNU Library General Public | 
|  | * License as published by the Free Software Foundation; either | 
|  | * version 2 of the License. | 
|  | * | 
|  | * This library is distributed in the hope that it will be useful, | 
|  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
|  | * Library General Public License for more details. | 
|  | * | 
|  | * You should have received a copy of the GNU Library General Public License | 
|  | * along with this library; see the file COPYING.LIB.  If not, write to | 
|  | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | 
|  | * Boston, MA 02110-1301, USA. | 
|  | */ | 
|  |  | 
|  | #include "config.h" | 
|  | #include "FixedTableLayout.h" | 
|  |  | 
|  | #include "RenderTable.h" | 
|  | #include "RenderTableCell.h" | 
|  | #include "RenderTableCol.h" | 
|  | #include "RenderTableSection.h" | 
|  |  | 
|  | /* | 
|  | The text below is from the CSS 2.1 specs. | 
|  |  | 
|  | Fixed table layout | 
|  |  | 
|  | With this (fast) algorithm, the horizontal layout of the table does | 
|  | not depend on the contents of the cells; it only depends on the | 
|  | table's width, the width of the columns, and borders or cell | 
|  | spacing. | 
|  |  | 
|  | The table's width may be specified explicitly with the 'width' | 
|  | property. A value of 'auto' (for both 'display: table' and 'display: | 
|  | inline-table') means use the automatic table layout algorithm. | 
|  |  | 
|  | In the fixed table layout algorithm, the width of each column is | 
|  | determined as follows: | 
|  |  | 
|  | 1. A column element with a value other than 'auto' for the 'width' | 
|  | property sets the width for that column. | 
|  |  | 
|  | 2. Otherwise, a cell in the first row with a value other than | 
|  | 'auto' for the 'width' property sets the width for that column. If | 
|  | the cell spans more than one column, the width is divided over the | 
|  | columns. | 
|  |  | 
|  | 3. Any remaining columns equally divide the remaining horizontal | 
|  | table space (minus borders or cell spacing). | 
|  |  | 
|  | The width of the table is then the greater of the value of the | 
|  | 'width' property for the table element and the sum of the column | 
|  | widths (plus cell spacing or borders). If the table is wider than | 
|  | the columns, the extra space should be distributed over the columns. | 
|  |  | 
|  |  | 
|  | In this manner, the user agent can begin to lay out the table once | 
|  | the entire first row has been received. Cells in subsequent rows do | 
|  | not affect column widths. Any cell that has content that overflows | 
|  | uses the 'overflow' property to determine whether to clip the | 
|  | overflow content. | 
|  | */ | 
|  |  | 
|  | namespace WebCore { | 
|  |  | 
|  | FixedTableLayout::FixedTableLayout(RenderTable* table) | 
|  | : TableLayout(table) | 
|  | { | 
|  | } | 
|  |  | 
|  | float FixedTableLayout::calcWidthArray() | 
|  | { | 
|  | // FIXME: We might want to wait until we have all of the first row before computing for the first time. | 
|  | float usedWidth = 0; | 
|  |  | 
|  | // iterate over all <col> elements | 
|  | unsigned nEffCols = m_table->numEffCols(); | 
|  | m_width.resize(nEffCols); | 
|  | m_width.fill(Length(Auto)); | 
|  |  | 
|  | unsigned currentEffectiveColumn = 0; | 
|  | for (RenderTableCol* col = m_table->firstColumn(); col; col = col->nextColumn()) { | 
|  | // RenderTableCols don't have the concept of preferred logical width, but we need to clear their dirty bits | 
|  | // so that if we call setPreferredWidthsDirty(true) on a col or one of its descendants, we'll mark it's | 
|  | // ancestors as dirty. | 
|  | col->clearPreferredLogicalWidthsDirtyBits(); | 
|  |  | 
|  | // Width specified by column-groups that have column child does not affect column width in fixed layout tables | 
|  | if (col->isTableColumnGroupWithColumnChildren()) | 
|  | continue; | 
|  |  | 
|  | Length colStyleLogicalWidth = col->style().logicalWidth(); | 
|  | float effectiveColWidth = 0; | 
|  | if (colStyleLogicalWidth.isFixed() && colStyleLogicalWidth.value() > 0) | 
|  | effectiveColWidth = colStyleLogicalWidth.value(); | 
|  |  | 
|  | unsigned span = col->span(); | 
|  | while (span) { | 
|  | unsigned spanInCurrentEffectiveColumn; | 
|  | if (currentEffectiveColumn >= nEffCols) { | 
|  | m_table->appendColumn(span); | 
|  | nEffCols++; | 
|  | m_width.append(Length()); | 
|  | spanInCurrentEffectiveColumn = span; | 
|  | } else { | 
|  | if (span < m_table->spanOfEffCol(currentEffectiveColumn)) { | 
|  | m_table->splitColumn(currentEffectiveColumn, span); | 
|  | nEffCols++; | 
|  | m_width.append(Length()); | 
|  | } | 
|  | spanInCurrentEffectiveColumn = m_table->spanOfEffCol(currentEffectiveColumn); | 
|  | } | 
|  | if ((colStyleLogicalWidth.isFixed() || colStyleLogicalWidth.isPercentOrCalculated()) && colStyleLogicalWidth.isPositive()) { | 
|  | m_width[currentEffectiveColumn] = colStyleLogicalWidth; | 
|  | m_width[currentEffectiveColumn] *= spanInCurrentEffectiveColumn; | 
|  | usedWidth += effectiveColWidth * spanInCurrentEffectiveColumn; | 
|  | } | 
|  | span -= spanInCurrentEffectiveColumn; | 
|  | currentEffectiveColumn++; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Iterate over the first row in case some are unspecified. | 
|  | RenderTableSection* section = m_table->topNonEmptySection(); | 
|  | if (!section) | 
|  | return usedWidth; | 
|  |  | 
|  | unsigned currentColumn = 0; | 
|  |  | 
|  | RenderTableRow* firstRow = section->firstRow(); | 
|  | for (RenderTableCell* cell = firstRow->firstCell(); cell; cell = cell->nextCell()) { | 
|  | Length logicalWidth = cell->styleOrColLogicalWidth(); | 
|  | unsigned span = cell->colSpan(); | 
|  | float fixedBorderBoxLogicalWidth = 0; | 
|  | // FIXME: Support other length types. If the width is non-auto, it should probably just use | 
|  | // RenderBox::computeLogicalWidthInFragmentUsing to compute the width. | 
|  | if (logicalWidth.isFixed() && logicalWidth.isPositive()) { | 
|  | fixedBorderBoxLogicalWidth = cell->adjustBorderBoxLogicalWidthForBoxSizing(logicalWidth.value()); | 
|  | logicalWidth.setValue(Fixed, fixedBorderBoxLogicalWidth); | 
|  | } | 
|  |  | 
|  | unsigned usedSpan = 0; | 
|  | while (usedSpan < span && currentColumn < nEffCols) { | 
|  | float eSpan = m_table->spanOfEffCol(currentColumn); | 
|  | // Only set if no col element has already set it. | 
|  | if (m_width[currentColumn].isAuto() && logicalWidth.type() != Auto) { | 
|  | m_width[currentColumn] = logicalWidth; | 
|  | m_width[currentColumn] *= eSpan / span; | 
|  | usedWidth += fixedBorderBoxLogicalWidth * eSpan / span; | 
|  | } | 
|  | usedSpan += eSpan; | 
|  | ++currentColumn; | 
|  | } | 
|  |  | 
|  | // FixedTableLayout doesn't use min/maxPreferredLogicalWidths, but we need to clear the | 
|  | // dirty bit on the cell so that we'll correctly mark its ancestors dirty | 
|  | // in case we later call setPreferredLogicalWidthsDirty(true) on it later. | 
|  | if (cell->preferredLogicalWidthsDirty()) | 
|  | cell->setPreferredLogicalWidthsDirty(false); | 
|  | } | 
|  |  | 
|  | return usedWidth; | 
|  | } | 
|  |  | 
|  | void FixedTableLayout::computeIntrinsicLogicalWidths(LayoutUnit& minWidth, LayoutUnit& maxWidth) | 
|  | { | 
|  | minWidth = maxWidth = calcWidthArray(); | 
|  | } | 
|  |  | 
|  | void FixedTableLayout::applyPreferredLogicalWidthQuirks(LayoutUnit& minWidth, LayoutUnit& maxWidth) const | 
|  | { | 
|  | Length tableLogicalWidth = m_table->style().logicalWidth(); | 
|  | if (tableLogicalWidth.isFixed() && tableLogicalWidth.isPositive()) | 
|  | minWidth = maxWidth = std::max(minWidth, LayoutUnit(tableLogicalWidth.value()) - m_table->bordersPaddingAndSpacingInRowDirection()); | 
|  |  | 
|  | /* | 
|  | <table style="width:100%; background-color:red"><tr><td> | 
|  | <table style="background-color:blue"><tr><td> | 
|  | <table style="width:100%; background-color:green; table-layout:fixed"><tr><td> | 
|  | Content | 
|  | </td></tr></table> | 
|  | </td></tr></table> | 
|  | </td></tr></table> | 
|  | */ | 
|  | // In this example, the two inner tables should be as large as the outer table. | 
|  | // We can achieve this effect by making the maxwidth of fixed tables with percentage | 
|  | // widths be infinite. | 
|  | if (m_table->style().logicalWidth().isPercentOrCalculated() && maxWidth < tableMaxWidth) | 
|  | maxWidth = tableMaxWidth; | 
|  | } | 
|  |  | 
|  | void FixedTableLayout::layout() | 
|  | { | 
|  | float tableLogicalWidth = m_table->logicalWidth() - m_table->bordersPaddingAndSpacingInRowDirection(); | 
|  | unsigned nEffCols = m_table->numEffCols(); | 
|  |  | 
|  | // FIXME: It is possible to be called without having properly updated our internal representation. | 
|  | // This means that our preferred logical widths were not recomputed as expected. | 
|  | if (nEffCols != m_width.size()) { | 
|  | calcWidthArray(); | 
|  | // FIXME: Table layout shouldn't modify our table structure (but does due to columns and column-groups). | 
|  | nEffCols = m_table->numEffCols(); | 
|  | } | 
|  |  | 
|  | Vector<float> calcWidth(nEffCols, 0); | 
|  |  | 
|  | unsigned numAuto = 0; | 
|  | unsigned autoSpan = 0; | 
|  | float totalFixedWidth = 0; | 
|  | float totalPercentWidth = 0; | 
|  | float totalPercent = 0; | 
|  |  | 
|  | // Compute requirements and try to satisfy fixed and percent widths. | 
|  | // Percentages are of the table's width, so for example | 
|  | // for a table width of 100px with columns (40px, 10%), the 10% compute | 
|  | // to 10px here, and will scale up to 20px in the final (80px, 20px). | 
|  | for (unsigned i = 0; i < nEffCols; i++) { | 
|  | if (m_width[i].isFixed()) { | 
|  | calcWidth[i] = m_width[i].value(); | 
|  | totalFixedWidth += calcWidth[i]; | 
|  | } else if (m_width[i].isPercent()) { | 
|  | calcWidth[i] = valueForLength(m_width[i], tableLogicalWidth); | 
|  | totalPercentWidth += calcWidth[i]; | 
|  | totalPercent += m_width[i].percent(); | 
|  | } else if (m_width[i].isAuto()) { | 
|  | numAuto++; | 
|  | autoSpan += m_table->spanOfEffCol(i); | 
|  | } | 
|  | } | 
|  |  | 
|  | float hspacing = m_table->hBorderSpacing(); | 
|  | float totalWidth = totalFixedWidth + totalPercentWidth; | 
|  | if (!numAuto || totalWidth > tableLogicalWidth) { | 
|  | // If there are no auto columns, or if the total is too wide, take | 
|  | // what we have and scale it to fit as necessary. | 
|  | if (totalWidth != tableLogicalWidth) { | 
|  | // Fixed widths only scale up | 
|  | if (totalFixedWidth && totalWidth < tableLogicalWidth) { | 
|  | totalFixedWidth = 0; | 
|  | for (unsigned i = 0; i < nEffCols; i++) { | 
|  | if (m_width[i].isFixed()) { | 
|  | calcWidth[i] = calcWidth[i] * tableLogicalWidth / totalWidth; | 
|  | totalFixedWidth += calcWidth[i]; | 
|  | } | 
|  | } | 
|  | } | 
|  | if (totalPercent) { | 
|  | totalPercentWidth = 0; | 
|  | for (unsigned i = 0; i < nEffCols; i++) { | 
|  | if (m_width[i].isPercent()) { | 
|  | calcWidth[i] = m_width[i].percent() * (tableLogicalWidth - totalFixedWidth) / totalPercent; | 
|  | totalPercentWidth += calcWidth[i]; | 
|  | } | 
|  | } | 
|  | } | 
|  | totalWidth = totalFixedWidth + totalPercentWidth; | 
|  | } | 
|  | } else { | 
|  | // Divide the remaining width among the auto columns. | 
|  | ASSERT(autoSpan >= numAuto); | 
|  | float remainingWidth = tableLogicalWidth - totalFixedWidth - totalPercentWidth - hspacing * (autoSpan - numAuto); | 
|  | int lastAuto = 0; | 
|  | for (unsigned i = 0; i < nEffCols; i++) { | 
|  | if (m_width[i].isAuto()) { | 
|  | unsigned span = m_table->spanOfEffCol(i); | 
|  | float w = remainingWidth * span / autoSpan; | 
|  | calcWidth[i] = w + hspacing * (span - 1); | 
|  | remainingWidth -= w; | 
|  | if (!remainingWidth) | 
|  | break; | 
|  | lastAuto = i; | 
|  | numAuto--; | 
|  | ASSERT(autoSpan >= span); | 
|  | autoSpan -= span; | 
|  | } | 
|  | } | 
|  | // Last one gets the remainder. | 
|  | if (remainingWidth) | 
|  | calcWidth[lastAuto] += remainingWidth; | 
|  | totalWidth = tableLogicalWidth; | 
|  | } | 
|  |  | 
|  | if (totalWidth < tableLogicalWidth) { | 
|  | // Spread extra space over columns. | 
|  | float remainingWidth = tableLogicalWidth - totalWidth; | 
|  | int total = nEffCols; | 
|  | while (total) { | 
|  | float w = remainingWidth / total; | 
|  | remainingWidth -= w; | 
|  | calcWidth[--total] += w; | 
|  | } | 
|  | if (nEffCols > 0) | 
|  | calcWidth[nEffCols - 1] += remainingWidth; | 
|  | } | 
|  |  | 
|  | float pos = 0; | 
|  | for (unsigned i = 0; i < nEffCols; i++) { | 
|  | m_table->setColumnPosition(i, pos); | 
|  | pos += calcWidth[i] + hspacing; | 
|  | } | 
|  | float colPositionsSize = m_table->columnPositions().size(); | 
|  | if (colPositionsSize > 0) | 
|  | m_table->setColumnPosition(colPositionsSize - 1, pos); | 
|  | } | 
|  |  | 
|  | } // namespace WebCore |