| /* |
| * 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, 2013 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 "core/layout/TableLayoutAlgorithmFixed.h" |
| |
| #include "core/layout/LayoutTable.h" |
| #include "core/layout/LayoutTableCell.h" |
| #include "core/layout/LayoutTableCol.h" |
| #include "core/layout/LayoutTableSection.h" |
| #include "platform/LayoutUnit.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 blink { |
| |
| TableLayoutAlgorithmFixed::TableLayoutAlgorithmFixed(LayoutTable* table) |
| : TableLayoutAlgorithm(table) {} |
| |
| int TableLayoutAlgorithmFixed::calcWidthArray() { |
| // FIXME: We might want to wait until we have all of the first row before |
| // computing for the first time. |
| int usedWidth = 0; |
| |
| // iterate over all <col> elements |
| unsigned nEffCols = m_table->numEffectiveColumns(); |
| m_width.resize(nEffCols); |
| m_width.fill(Length(Auto)); |
| |
| unsigned currentEffectiveColumn = 0; |
| for (LayoutTableCol* col = m_table->firstColumn(); col; |
| col = col->nextColumn()) { |
| // LayoutTableCols 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(); |
| int effectiveColWidth = 0; |
| if (colStyleLogicalWidth.isFixed() && colStyleLogicalWidth.value() > 0) |
| effectiveColWidth = colStyleLogicalWidth.value(); |
| |
| unsigned span = col->span(); |
| while (span) { |
| unsigned spanInCurrentEffectiveColumn; |
| if (currentEffectiveColumn >= nEffCols) { |
| m_table->appendEffectiveColumn(span); |
| nEffCols++; |
| m_width.append(Length()); |
| spanInCurrentEffectiveColumn = span; |
| } else { |
| if (span < m_table->spanOfEffectiveColumn(currentEffectiveColumn)) { |
| m_table->splitEffectiveColumn(currentEffectiveColumn, span); |
| nEffCols++; |
| m_width.append(Length()); |
| } |
| spanInCurrentEffectiveColumn = |
| m_table->spanOfEffectiveColumn(currentEffectiveColumn); |
| } |
| // TODO(alancutter): Make this work correctly for calc lengths. |
| if ((colStyleLogicalWidth.isFixed() || |
| colStyleLogicalWidth.isPercentOrCalc()) && |
| 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. |
| LayoutTableSection* section = m_table->topNonEmptySection(); |
| if (!section) |
| return usedWidth; |
| |
| unsigned currentColumn = 0; |
| |
| LayoutTableRow* firstRow = section->firstRow(); |
| for (LayoutTableCell* cell = firstRow->firstCell(); cell; |
| cell = cell->nextCell()) { |
| Length logicalWidth = cell->styleOrColLogicalWidth(); |
| |
| // FIXME: calc() on tables should be handled consistently with other |
| // lengths. See bug: https://crbug.com/382725 |
| if (logicalWidth.isCalculated()) |
| logicalWidth = Length(); // Make it Auto |
| |
| unsigned span = cell->colSpan(); |
| int fixedBorderBoxLogicalWidth = 0; |
| // FIXME: Support other length types. If the width is non-auto, it should |
| // probably just use LayoutBox::computeLogicalWidthUsing to compute the |
| // width. |
| if (logicalWidth.isFixed() && logicalWidth.isPositive()) { |
| fixedBorderBoxLogicalWidth = |
| cell->adjustBorderBoxLogicalWidthForBoxSizing(logicalWidth.value()) |
| .toInt(); |
| logicalWidth.setValue(fixedBorderBoxLogicalWidth); |
| } |
| |
| unsigned usedSpan = 0; |
| while (usedSpan < span && currentColumn < nEffCols) { |
| float eSpan = m_table->spanOfEffectiveColumn(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; |
| } |
| |
| // TableLayoutAlgorithmFixed 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() on it later. |
| if (cell->preferredLogicalWidthsDirty()) |
| cell->clearPreferredLogicalWidthsDirty(); |
| } |
| |
| return usedWidth; |
| } |
| |
| void TableLayoutAlgorithmFixed::computeIntrinsicLogicalWidths( |
| LayoutUnit& minWidth, |
| LayoutUnit& maxWidth) { |
| minWidth = maxWidth = LayoutUnit(calcWidthArray()); |
| } |
| |
| void TableLayoutAlgorithmFixed::applyPreferredLogicalWidthQuirks( |
| LayoutUnit& minWidth, |
| LayoutUnit& maxWidth) const { |
| Length tableLogicalWidth = m_table->style()->logicalWidth(); |
| if (tableLogicalWidth.isFixed() && tableLogicalWidth.isPositive()) { |
| minWidth = maxWidth = LayoutUnit( |
| max(minWidth, |
| LayoutUnit(tableLogicalWidth.value() - |
| m_table->bordersPaddingAndSpacingInRowDirection())) |
| .floor()); |
| } |
| |
| /* |
| <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().isPercentOrCalc() && |
| maxWidth < tableMaxWidth) |
| maxWidth = LayoutUnit(tableMaxWidth); |
| } |
| |
| void TableLayoutAlgorithmFixed::layout() { |
| int tableLogicalWidth = (m_table->logicalWidth() - |
| m_table->bordersPaddingAndSpacingInRowDirection()) |
| .toInt(); |
| unsigned nEffCols = m_table->numEffectiveColumns(); |
| |
| // 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->numEffectiveColumns(); |
| } |
| |
| Vector<int> calcWidth(nEffCols, 0); |
| |
| unsigned numAuto = 0; |
| unsigned autoSpan = 0; |
| int totalFixedWidth = 0; |
| int 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].isPercentOrCalc()) { |
| // TODO(alancutter): Make this work correctly for calc lengths. |
| calcWidth[i] = |
| valueForLength(m_width[i], LayoutUnit(tableLogicalWidth)).toInt(); |
| totalPercentWidth += calcWidth[i]; |
| totalPercent += m_width[i].percent(); |
| } else if (m_width[i].isAuto()) { |
| numAuto++; |
| autoSpan += m_table->spanOfEffectiveColumn(i); |
| } |
| } |
| |
| int hspacing = m_table->hBorderSpacing(); |
| int 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++) { |
| // TODO(alancutter): Make this work correctly for calc lengths. |
| if (m_width[i].isPercentOrCalc()) { |
| 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); |
| int 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->spanOfEffectiveColumn(i); |
| int 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. |
| int remainingWidth = tableLogicalWidth - totalWidth; |
| int total = nEffCols; |
| while (total) { |
| int w = remainingWidth / total; |
| remainingWidth -= w; |
| calcWidth[--total] += w; |
| } |
| if (nEffCols > 0) |
| calcWidth[nEffCols - 1] += remainingWidth; |
| } |
| |
| ASSERT(m_table->effectiveColumnPositions().size() == nEffCols + 1); |
| int pos = 0; |
| for (unsigned i = 0; i < nEffCols; i++) { |
| m_table->setEffectiveColumnPosition(i, pos); |
| pos += calcWidth[i] + hspacing; |
| } |
| // The extra position is for the imaginary column after the last column. |
| m_table->setEffectiveColumnPosition(nEffCols, pos); |
| } |
| |
| void TableLayoutAlgorithmFixed::willChangeTableLayout() { |
| // When switching table layout algorithm, we need to dirty the preferred |
| // logical widths as we cleared the bits without computing them. |
| // (see calcWidthArray above.) This optimization is preferred to always |
| // computing the logical widths we never intended to use. |
| m_table->recalcSectionsIfNeeded(); |
| m_table->markAllCellsWidthsDirtyAndOrNeedsLayout(LayoutTable::MarkDirtyOnly); |
| } |
| |
| } // namespace blink |