diff options
Diffstat (limited to 'layout/tables/FixedTableLayoutStrategy.cpp')
-rw-r--r-- | layout/tables/FixedTableLayoutStrategy.cpp | 402 |
1 files changed, 402 insertions, 0 deletions
diff --git a/layout/tables/FixedTableLayoutStrategy.cpp b/layout/tables/FixedTableLayoutStrategy.cpp new file mode 100644 index 0000000000..8d74e3ba12 --- /dev/null +++ b/layout/tables/FixedTableLayoutStrategy.cpp @@ -0,0 +1,402 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:cindent:ts=2:et:sw=2: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Algorithms that determine column and table inline sizes used for + * CSS2's 'table-layout: fixed'. + */ + +#include "FixedTableLayoutStrategy.h" + +#include "nsLayoutUtils.h" +#include "nsStyleConsts.h" +#include "nsTableFrame.h" +#include "nsTableColFrame.h" +#include "nsTableCellFrame.h" +#include "WritingModes.h" +#include <algorithm> + +using namespace mozilla; + +FixedTableLayoutStrategy::FixedTableLayoutStrategy(nsTableFrame* aTableFrame) + : nsITableLayoutStrategy(nsITableLayoutStrategy::Fixed), + mTableFrame(aTableFrame) { + MarkIntrinsicISizesDirty(); +} + +/* virtual */ +FixedTableLayoutStrategy::~FixedTableLayoutStrategy() = default; + +/* virtual */ +nscoord FixedTableLayoutStrategy::GetMinISize(gfxContext* aRenderingContext) { + DISPLAY_MIN_INLINE_SIZE(mTableFrame, mMinISize); + if (mMinISize != NS_INTRINSIC_ISIZE_UNKNOWN) { + return mMinISize; + } + + // It's theoretically possible to do something much better here that + // depends only on the columns and the first row (where we look at + // intrinsic inline sizes inside the first row and then reverse the + // algorithm to find the narrowest inline size that would hold all of + // those intrinsic inline sizes), but it wouldn't be compatible with + // other browsers, or with the use of GetMinISize by + // nsTableFrame::ComputeSize to determine the inline size of a fixed + // layout table, since CSS2.1 says: + // 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). + + // XXX Should we really ignore 'min-inline-size' and 'max-inline-size'? + // XXX Should we really ignore inline sizes on column groups? + + nsTableCellMap* cellMap = mTableFrame->GetCellMap(); + int32_t colCount = cellMap->GetColCount(); + + nscoord result = 0; + + if (colCount > 0) { + result += mTableFrame->GetColSpacing(-1, colCount); + } + + WritingMode wm = mTableFrame->GetWritingMode(); + for (int32_t col = 0; col < colCount; ++col) { + nsTableColFrame* colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + nscoord spacing = mTableFrame->GetColSpacing(col); + const auto* styleISize = &colFrame->StylePosition()->ISize(wm); + if (styleISize->ConvertsToLength()) { + result += styleISize->ToLength(); + } else if (styleISize->ConvertsToPercentage()) { + // do nothing + } else { + // The 'table-layout: fixed' algorithm considers only cells in the + // first row. + bool originates; + int32_t colSpan; + nsTableCellFrame* cellFrame = + cellMap->GetCellInfoAt(0, col, &originates, &colSpan); + if (cellFrame) { + styleISize = &cellFrame->StylePosition()->ISize(wm); + if (styleISize->ConvertsToLength() || styleISize->IsMinContent() || + styleISize->IsMaxContent()) { + nscoord cellISize = nsLayoutUtils::IntrinsicForContainer( + aRenderingContext, cellFrame, IntrinsicISizeType::MinISize); + if (colSpan > 1) { + // If a column-spanning cell is in the first row, split up + // the space evenly. (XXX This isn't quite right if some of + // the columns it's in have specified inline sizes. Should + // we care?) + cellISize = ((cellISize + spacing) / colSpan) - spacing; + } + result += cellISize; + } else if (styleISize->ConvertsToPercentage()) { + if (colSpan > 1) { + // XXX Can this force columns to negative inline sizes? + result -= spacing * (colSpan - 1); + } + } + // else, for 'auto', '-moz-available', '-moz-fit-content', + // and 'calc()' with both lengths and percentages, do nothing + } + } + } + + return (mMinISize = result); +} + +/* virtual */ +nscoord FixedTableLayoutStrategy::GetPrefISize(gfxContext* aRenderingContext, + bool aComputingSize) { + // It's theoretically possible to do something much better here that + // depends only on the columns and the first row (where we look at + // intrinsic inline sizes inside the first row and then reverse the + // algorithm to find the narrowest inline size that would hold all of + // those intrinsic inline sizes), but it wouldn't be compatible with + // other browsers. + nscoord result = nscoord_MAX; + DISPLAY_PREF_INLINE_SIZE(mTableFrame, result); + return result; +} + +/* virtual */ +void FixedTableLayoutStrategy::MarkIntrinsicISizesDirty() { + mMinISize = NS_INTRINSIC_ISIZE_UNKNOWN; + mLastCalcISize = nscoord_MIN; +} + +static inline nscoord AllocateUnassigned(nscoord aUnassignedSpace, + float aShare) { + if (aShare == 1.0f) { + // This happens when the numbers we're dividing to get aShare are + // equal. We want to return unassignedSpace exactly, even if it + // can't be precisely round-tripped through float. + return aUnassignedSpace; + } + return NSToCoordRound(float(aUnassignedSpace) * aShare); +} + +/* virtual */ +void FixedTableLayoutStrategy::ComputeColumnISizes( + const ReflowInput& aReflowInput) { + nscoord tableISize = aReflowInput.ComputedISize(); + + if (mLastCalcISize == tableISize) { + return; + } + mLastCalcISize = tableISize; + + nsTableCellMap* cellMap = mTableFrame->GetCellMap(); + int32_t colCount = cellMap->GetColCount(); + + if (colCount == 0) { + // No Columns - nothing to compute + return; + } + + // border-spacing isn't part of the basis for percentages. + tableISize -= mTableFrame->GetColSpacing(-1, colCount); + + // store the old column inline sizes. We might call SetFinalISize + // multiple times on the columns, due to this we can't compare at the + // last call that the inline size has changed with respect to the last + // call to ComputeColumnISizes. In order to overcome this we store the + // old values in this array. A single call to SetFinalISize would make + // it possible to call GetFinalISize before and to compare when + // setting the final inline size. + nsTArray<nscoord> oldColISizes; + + // XXX This ignores the 'min-width' and 'max-width' properties + // throughout. Then again, that's what the CSS spec says to do. + + // XXX Should we really ignore widths on column groups? + + uint32_t unassignedCount = 0; + nscoord unassignedSpace = tableISize; + const nscoord unassignedMarker = nscoord_MIN; + + // We use the PrefPercent on the columns to store the percentages + // used to compute column inline sizes in case we need to shrink or + // expand the columns. + float pctTotal = 0.0f; + + // Accumulate the total specified (non-percent) on the columns for + // distributing excess inline size to the columns. + nscoord specTotal = 0; + + WritingMode wm = mTableFrame->GetWritingMode(); + for (int32_t col = 0; col < colCount; ++col) { + nsTableColFrame* colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + oldColISizes.AppendElement(0); + NS_ERROR("column frames out of sync with cell map"); + continue; + } + oldColISizes.AppendElement(colFrame->GetFinalISize()); + colFrame->ResetPrefPercent(); + const auto* styleISize = &colFrame->StylePosition()->ISize(wm); + nscoord colISize; + if (styleISize->ConvertsToLength()) { + colISize = styleISize->ToLength(); + specTotal += colISize; + } else if (styleISize->ConvertsToPercentage()) { + float pct = styleISize->ToPercentage(); + colISize = NSToCoordFloor(pct * float(tableISize)); + colFrame->AddPrefPercent(pct); + pctTotal += pct; + } else { + // The 'table-layout: fixed' algorithm considers only cells in the + // first row. + bool originates; + int32_t colSpan; + nsTableCellFrame* cellFrame = + cellMap->GetCellInfoAt(0, col, &originates, &colSpan); + if (cellFrame) { + const nsStylePosition* cellStylePos = cellFrame->StylePosition(); + styleISize = &cellStylePos->ISize(wm); + if (styleISize->ConvertsToLength() || styleISize->IsMaxContent() || + styleISize->IsMinContent()) { + // XXX This should use real percentage padding + // Note that the difference between MinISize and PrefISize + // shouldn't matter for any of these values of styleISize; use + // MIN_ISIZE for symmetry with GetMinISize above, just in case + // there is a difference. + colISize = nsLayoutUtils::IntrinsicForContainer( + aReflowInput.mRenderingContext, cellFrame, + IntrinsicISizeType::MinISize); + } else if (styleISize->ConvertsToPercentage()) { + // XXX This should use real percentage padding + float pct = styleISize->ToPercentage(); + colISize = NSToCoordFloor(pct * float(tableISize)); + + if (cellStylePos->mBoxSizing == StyleBoxSizing::Content) { + nsIFrame::IntrinsicSizeOffsetData offsets = + cellFrame->IntrinsicISizeOffsets(); + colISize += offsets.padding + offsets.border; + } + + pct /= float(colSpan); + colFrame->AddPrefPercent(pct); + pctTotal += pct; + } else { + // 'auto', '-moz-available', '-moz-fit-content', and 'calc()' + // with percentages + colISize = unassignedMarker; + } + if (colISize != unassignedMarker) { + if (colSpan > 1) { + // If a column-spanning cell is in the first row, split up + // the space evenly. (XXX This isn't quite right if some of + // the columns it's in have specified iSizes. Should we + // care?) + nscoord spacing = mTableFrame->GetColSpacing(col); + colISize = ((colISize + spacing) / colSpan) - spacing; + if (colISize < 0) { + colISize = 0; + } + } + if (!styleISize->ConvertsToPercentage()) { + specTotal += colISize; + } + } + } else { + colISize = unassignedMarker; + } + } + + colFrame->SetFinalISize(colISize); + + if (colISize == unassignedMarker) { + ++unassignedCount; + } else { + unassignedSpace -= colISize; + } + } + + if (unassignedSpace < 0) { + if (pctTotal > 0) { + // If the columns took up too much space, reduce those that had + // percentage inline sizes. The spec doesn't say to do this, but + // we've always done it in the past, and so does WinIE6. + nscoord pctUsed = NSToCoordFloor(pctTotal * float(tableISize)); + nscoord reduce = std::min(pctUsed, -unassignedSpace); + float reduceRatio = float(reduce) / pctTotal; + for (int32_t col = 0; col < colCount; ++col) { + nsTableColFrame* colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + nscoord colISize = colFrame->GetFinalISize(); + colISize -= NSToCoordFloor(colFrame->GetPrefPercent() * reduceRatio); + if (colISize < 0) { + colISize = 0; + } + colFrame->SetFinalISize(colISize); + } + } + unassignedSpace = 0; + } + + if (unassignedCount > 0) { + // The spec says to distribute the remaining space evenly among + // the columns. + nscoord toAssign = unassignedSpace / unassignedCount; + for (int32_t col = 0; col < colCount; ++col) { + nsTableColFrame* colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + if (colFrame->GetFinalISize() == unassignedMarker) { + colFrame->SetFinalISize(toAssign); + } + } + } else if (unassignedSpace > 0) { + // The spec doesn't say how to distribute the unassigned space. + if (specTotal > 0) { + // Distribute proportionally to non-percentage columns. + nscoord specUndist = specTotal; + for (int32_t col = 0; col < colCount; ++col) { + nsTableColFrame* colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + if (colFrame->GetPrefPercent() == 0.0f) { + NS_ASSERTION(colFrame->GetFinalISize() <= specUndist, + "inline sizes don't add up"); + nscoord toAdd = AllocateUnassigned( + unassignedSpace, + float(colFrame->GetFinalISize()) / float(specUndist)); + specUndist -= colFrame->GetFinalISize(); + colFrame->SetFinalISize(colFrame->GetFinalISize() + toAdd); + unassignedSpace -= toAdd; + if (specUndist <= 0) { + NS_ASSERTION(specUndist == 0, "math should be exact"); + break; + } + } + } + NS_ASSERTION(unassignedSpace == 0, "failed to redistribute"); + } else if (pctTotal > 0) { + // Distribute proportionally to percentage columns. + float pctUndist = pctTotal; + for (int32_t col = 0; col < colCount; ++col) { + nsTableColFrame* colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + if (pctUndist < colFrame->GetPrefPercent()) { + // This can happen with floating-point math. + NS_ASSERTION(colFrame->GetPrefPercent() - pctUndist < 0.0001, + "inline sizes don't add up"); + pctUndist = colFrame->GetPrefPercent(); + } + nscoord toAdd = AllocateUnassigned( + unassignedSpace, colFrame->GetPrefPercent() / pctUndist); + colFrame->SetFinalISize(colFrame->GetFinalISize() + toAdd); + unassignedSpace -= toAdd; + pctUndist -= colFrame->GetPrefPercent(); + if (pctUndist <= 0.0f) { + break; + } + } + NS_ASSERTION(unassignedSpace == 0, "failed to redistribute"); + } else { + // Distribute equally to the zero-iSize columns. + int32_t colsRemaining = colCount; + for (int32_t col = 0; col < colCount; ++col) { + nsTableColFrame* colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + NS_ASSERTION(colFrame->GetFinalISize() == 0, "yikes"); + nscoord toAdd = + AllocateUnassigned(unassignedSpace, 1.0f / float(colsRemaining)); + colFrame->SetFinalISize(toAdd); + unassignedSpace -= toAdd; + --colsRemaining; + } + NS_ASSERTION(unassignedSpace == 0, "failed to redistribute"); + } + } + for (int32_t col = 0; col < colCount; ++col) { + nsTableColFrame* colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + if (oldColISizes.ElementAt(col) != colFrame->GetFinalISize()) { + mTableFrame->DidResizeColumns(); + break; + } + } +} |