diff options
Diffstat (limited to 'layout/tables/BasicTableLayoutStrategy.cpp')
-rw-r--r-- | layout/tables/BasicTableLayoutStrategy.cpp | 1019 |
1 files changed, 1019 insertions, 0 deletions
diff --git a/layout/tables/BasicTableLayoutStrategy.cpp b/layout/tables/BasicTableLayoutStrategy.cpp new file mode 100644 index 0000000000..f7cc36c75a --- /dev/null +++ b/layout/tables/BasicTableLayoutStrategy.cpp @@ -0,0 +1,1019 @@ +/* -*- 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/. */ + +/* + * Web-compatible algorithms that determine column and table widths, + * used for CSS2's 'table-layout: auto'. + */ + +#include "BasicTableLayoutStrategy.h" + +#include <algorithm> + +#include "nsTableFrame.h" +#include "nsTableColFrame.h" +#include "nsTableCellFrame.h" +#include "nsLayoutUtils.h" +#include "nsGkAtoms.h" +#include "SpanningCellSorter.h" +#include "nsIContent.h" + +using namespace mozilla; +using namespace mozilla::layout; + +#undef DEBUG_TABLE_STRATEGY + +BasicTableLayoutStrategy::BasicTableLayoutStrategy(nsTableFrame* aTableFrame) + : nsITableLayoutStrategy(nsITableLayoutStrategy::Auto), + mTableFrame(aTableFrame) { + MarkIntrinsicISizesDirty(); +} + +/* virtual */ +BasicTableLayoutStrategy::~BasicTableLayoutStrategy() = default; + +/* virtual */ +nscoord BasicTableLayoutStrategy::GetMinISize(gfxContext* aRenderingContext) { + DISPLAY_MIN_INLINE_SIZE(mTableFrame, mMinISize); + if (mMinISize == NS_INTRINSIC_ISIZE_UNKNOWN) { + ComputeIntrinsicISizes(aRenderingContext); + } + return mMinISize; +} + +/* virtual */ +nscoord BasicTableLayoutStrategy::GetPrefISize(gfxContext* aRenderingContext, + bool aComputingSize) { + DISPLAY_PREF_INLINE_SIZE(mTableFrame, mPrefISize); + NS_ASSERTION((mPrefISize == NS_INTRINSIC_ISIZE_UNKNOWN) == + (mPrefISizePctExpand == NS_INTRINSIC_ISIZE_UNKNOWN), + "dirtyness out of sync"); + if (mPrefISize == NS_INTRINSIC_ISIZE_UNKNOWN) { + ComputeIntrinsicISizes(aRenderingContext); + } + return aComputingSize ? mPrefISizePctExpand : mPrefISize; +} + +struct CellISizeInfo { + CellISizeInfo(nscoord aMinCoord, nscoord aPrefCoord, float aPrefPercent, + bool aHasSpecifiedISize) + : hasSpecifiedISize(aHasSpecifiedISize), + minCoord(aMinCoord), + prefCoord(aPrefCoord), + prefPercent(aPrefPercent) {} + + bool hasSpecifiedISize; + nscoord minCoord; + nscoord prefCoord; + float prefPercent; +}; + +// Used for both column and cell calculations. The parts needed only +// for cells are skipped when aIsCell is false. +static CellISizeInfo GetISizeInfo(gfxContext* aRenderingContext, + nsIFrame* aFrame, WritingMode aWM, + bool aIsCell) { + nscoord minCoord, prefCoord; + const nsStylePosition* stylePos = aFrame->StylePosition(); + bool isQuirks = + aFrame->PresContext()->CompatibilityMode() == eCompatibility_NavQuirks; + nscoord boxSizingToBorderEdge = 0; + if (aIsCell) { + // If aFrame is a container for font size inflation, then shrink + // wrapping inside of it should not apply font size inflation. + AutoMaybeDisableFontInflation an(aFrame); + + minCoord = aFrame->GetMinISize(aRenderingContext); + prefCoord = aFrame->GetPrefISize(aRenderingContext); + // Until almost the end of this function, minCoord and prefCoord + // represent the box-sizing based isize values (which mean they + // should include inline padding and border width when + // box-sizing is set to border-box). + // Note that this function returns border-box isize, we add the + // outer edges near the end of this function. + + // XXX Should we ignore percentage padding? + nsIFrame::IntrinsicSizeOffsetData offsets = aFrame->IntrinsicISizeOffsets(); + + // In quirks mode, table cell isize should be content-box, + // but bsize should be border box. + // Because of this historic anomaly, we do not use quirk.css. + // (We can't specify one value of box-sizing for isize and another + // for bsize). + // For this reason, we also do not use box-sizing for just one of + // them, as this may be confusing. + if (isQuirks || stylePos->mBoxSizing == StyleBoxSizing::Content) { + boxSizingToBorderEdge = offsets.padding + offsets.border; + } else { + // StyleBoxSizing::Border and standards-mode + minCoord += offsets.padding + offsets.border; + prefCoord += offsets.padding + offsets.border; + } + } else { + minCoord = 0; + prefCoord = 0; + } + float prefPercent = 0.0f; + bool hasSpecifiedISize = false; + + const auto& iSize = stylePos->ISize(aWM); + // NOTE: We're ignoring calc() units with both lengths and percentages here, + // for lack of a sensible idea for what to do with them. This means calc() + // with percentages is basically handled like 'auto' for table cells and + // columns. + if (iSize.ConvertsToLength()) { + hasSpecifiedISize = true; + nscoord c = iSize.ToLength(); + // Quirk: A cell with "nowrap" set and a coord value for the + // isize which is bigger than the intrinsic minimum isize uses + // that coord value as the minimum isize. + // This is kept up-to-date with dynamic changes to nowrap by code in + // nsTableCellFrame::AttributeChanged + if (aIsCell && c > minCoord && isQuirks && + aFrame->GetContent()->AsElement()->HasAttr(kNameSpaceID_None, + nsGkAtoms::nowrap)) { + minCoord = c; + } + prefCoord = std::max(c, minCoord); + } else if (iSize.ConvertsToPercentage()) { + prefPercent = iSize.ToPercentage(); + } else if (aIsCell) { + switch (iSize.tag) { + case StyleSize::Tag::MaxContent: + // 'inline-size' only affects pref isize, not min + // isize, so don't change anything + break; + case StyleSize::Tag::MinContent: + prefCoord = minCoord; + break; + case StyleSize::Tag::MozAvailable: + case StyleSize::Tag::FitContent: + case StyleSize::Tag::FitContentFunction: + // TODO: Bug 1708310: Make sure fit-content() work properly in table. + case StyleSize::Tag::Auto: + case StyleSize::Tag::LengthPercentage: + break; + } + } + + StyleMaxSize maxISize = stylePos->MaxISize(aWM); + if (nsIFrame::ToExtremumLength(maxISize)) { + if (!aIsCell || maxISize.IsMozAvailable()) { + maxISize = StyleMaxSize::None(); + } else if (maxISize.IsFitContent() || maxISize.IsFitContentFunction()) { + // TODO: Bug 1708310: Make sure fit-content() work properly in table. + // for 'max-inline-size', '-moz-fit-content' is like 'max-content' + maxISize = StyleMaxSize::MaxContent(); + } + } + // XXX To really implement 'max-inline-size' well, we'd need to store + // it separately on the columns. + const LogicalSize zeroSize(aWM); + if (maxISize.ConvertsToLength() || nsIFrame::ToExtremumLength(maxISize)) { + nscoord c = aFrame + ->ComputeISizeValue(aRenderingContext, aWM, zeroSize, + zeroSize, 0, maxISize) + .mISize; + minCoord = std::min(c, minCoord); + prefCoord = std::min(c, prefCoord); + } else if (maxISize.ConvertsToPercentage()) { + float p = maxISize.ToPercentage(); + if (p < prefPercent) { + prefPercent = p; + } + } + + StyleSize minISize = stylePos->MinISize(aWM); + if (nsIFrame::ToExtremumLength(maxISize)) { + if (!aIsCell || minISize.IsMozAvailable()) { + minISize = StyleSize::LengthPercentage(LengthPercentage::Zero()); + } else if (minISize.IsFitContent() || minISize.IsFitContentFunction()) { + // TODO: Bug 1708310: Make sure fit-content() work properly in table. + // for 'min-inline-size', '-moz-fit-content' is like 'min-content' + minISize = StyleSize::MinContent(); + } + } + + if (minISize.ConvertsToLength() || nsIFrame::ToExtremumLength(minISize)) { + nscoord c = aFrame + ->ComputeISizeValue(aRenderingContext, aWM, zeroSize, + zeroSize, 0, minISize) + .mISize; + minCoord = std::max(c, minCoord); + prefCoord = std::max(c, prefCoord); + } else if (minISize.ConvertsToPercentage()) { + float p = minISize.ToPercentage(); + if (p > prefPercent) { + prefPercent = p; + } + } + + // XXX Should col frame have border/padding considered? + if (aIsCell) { + minCoord += boxSizingToBorderEdge; + prefCoord = NSCoordSaturatingAdd(prefCoord, boxSizingToBorderEdge); + } + + return CellISizeInfo(minCoord, prefCoord, prefPercent, hasSpecifiedISize); +} + +static inline CellISizeInfo GetCellISizeInfo(gfxContext* aRenderingContext, + nsTableCellFrame* aCellFrame, + WritingMode aWM) { + return GetISizeInfo(aRenderingContext, aCellFrame, aWM, true); +} + +static inline CellISizeInfo GetColISizeInfo(gfxContext* aRenderingContext, + nsIFrame* aFrame, WritingMode aWM) { + return GetISizeInfo(aRenderingContext, aFrame, aWM, false); +} + +/** + * The algorithm in this function, in addition to meeting the + * requirements of Web-compatibility, is also invariant under reordering + * of the rows within a table (something that most, but not all, other + * browsers are). + */ +void BasicTableLayoutStrategy::ComputeColumnIntrinsicISizes( + gfxContext* aRenderingContext) { + nsTableFrame* tableFrame = mTableFrame; + nsTableCellMap* cellMap = tableFrame->GetCellMap(); + WritingMode wm = tableFrame->GetWritingMode(); + + mozilla::AutoStackArena arena; + SpanningCellSorter spanningCells; + + // Loop over the columns to consider the columns and cells *without* + // a colspan. + int32_t col, col_end; + for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) { + nsTableColFrame* colFrame = tableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + colFrame->ResetIntrinsics(); + colFrame->ResetSpanIntrinsics(); + + // Consider the isizes on the column. + CellISizeInfo colInfo = GetColISizeInfo(aRenderingContext, colFrame, wm); + colFrame->AddCoords(colInfo.minCoord, colInfo.prefCoord, + colInfo.hasSpecifiedISize); + colFrame->AddPrefPercent(colInfo.prefPercent); + + // Consider the isizes on the column-group. Note that we follow + // what the HTML spec says here, and make the isize apply to + // each column in the group, not the group as a whole. + + // If column has isize, column-group doesn't override isize. + if (colInfo.minCoord == 0 && colInfo.prefCoord == 0 && + colInfo.prefPercent == 0.0f) { + NS_ASSERTION(colFrame->GetParent()->IsTableColGroupFrame(), + "expected a column-group"); + colInfo = GetColISizeInfo(aRenderingContext, colFrame->GetParent(), wm); + colFrame->AddCoords(colInfo.minCoord, colInfo.prefCoord, + colInfo.hasSpecifiedISize); + colFrame->AddPrefPercent(colInfo.prefPercent); + } + + // Consider the contents of and the isizes on the cells without + // colspans. + nsCellMapColumnIterator columnIter(cellMap, col); + int32_t row, colSpan; + nsTableCellFrame* cellFrame; + while ((cellFrame = columnIter.GetNextFrame(&row, &colSpan))) { + if (colSpan > 1) { + spanningCells.AddCell(colSpan, row, col); + continue; + } + + CellISizeInfo info = GetCellISizeInfo(aRenderingContext, cellFrame, wm); + + colFrame->AddCoords(info.minCoord, info.prefCoord, + info.hasSpecifiedISize); + colFrame->AddPrefPercent(info.prefPercent); + } +#ifdef DEBUG_dbaron_off + printf("table %p col %d nonspan: min=%d pref=%d spec=%d pct=%f\n", + mTableFrame, col, colFrame->GetMinCoord(), colFrame->GetPrefCoord(), + colFrame->GetHasSpecifiedCoord(), colFrame->GetPrefPercent()); +#endif + } +#ifdef DEBUG_TABLE_STRATEGY + printf("ComputeColumnIntrinsicISizes single\n"); + mTableFrame->Dump(false, true, false); +#endif + + // Consider the cells with a colspan that we saved in the loop above + // into the spanning cell sorter. We consider these cells by seeing + // if they require adding to the isizes resulting only from cells + // with a smaller colspan, and therefore we must process them sorted + // in increasing order by colspan. For each colspan group, we + // accumulate new values to accumulate in the column frame's Span* + // members. + // + // Considering things only relative to the isizes resulting from + // cells with smaller colspans (rather than incrementally including + // the results from spanning cells, or doing spanning and + // non-spanning cells in a single pass) means that layout remains + // row-order-invariant and (except for percentage isizes that add to + // more than 100%) column-order invariant. + // + // Starting with smaller colspans makes it more likely that we + // satisfy all the constraints given and don't distribute space to + // columns where we don't need it. + SpanningCellSorter::Item* item; + int32_t colSpan; + while ((item = spanningCells.GetNext(&colSpan))) { + NS_ASSERTION(colSpan > 1, + "cell should not have been put in spanning cell sorter"); + do { + int32_t row = item->row; + col = item->col; + CellData* cellData = cellMap->GetDataAt(row, col); + NS_ASSERTION(cellData && cellData->IsOrig(), + "bogus result from spanning cell sorter"); + + nsTableCellFrame* cellFrame = cellData->GetCellFrame(); + NS_ASSERTION(cellFrame, "bogus result from spanning cell sorter"); + + CellISizeInfo info = GetCellISizeInfo(aRenderingContext, cellFrame, wm); + + if (info.prefPercent > 0.0f) { + DistributePctISizeToColumns(info.prefPercent, col, colSpan); + } + DistributeISizeToColumns(info.minCoord, col, colSpan, BTLS_MIN_ISIZE, + info.hasSpecifiedISize); + DistributeISizeToColumns(info.prefCoord, col, colSpan, BTLS_PREF_ISIZE, + info.hasSpecifiedISize); + } while ((item = item->next)); + + // Combine the results of the span analysis into the main results, + // for each increment of colspan. + + for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) { + nsTableColFrame* colFrame = tableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + + colFrame->AccumulateSpanIntrinsics(); + colFrame->ResetSpanIntrinsics(); + +#ifdef DEBUG_dbaron_off + printf("table %p col %d span %d: min=%d pref=%d spec=%d pct=%f\n", + mTableFrame, col, colSpan, colFrame->GetMinCoord(), + colFrame->GetPrefCoord(), colFrame->GetHasSpecifiedCoord(), + colFrame->GetPrefPercent()); +#endif + } + } + + // Prevent percentages from adding to more than 100% by (to be + // compatible with other browsers) treating any percentages that would + // increase the total percentage to more than 100% as the number that + // would increase it to only 100% (which is 0% if we've already hit + // 100%). This means layout depends on the order of columns. + float pct_used = 0.0f; + for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) { + nsTableColFrame* colFrame = tableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + + colFrame->AdjustPrefPercent(&pct_used); + } + +#ifdef DEBUG_TABLE_STRATEGY + printf("ComputeColumnIntrinsicISizes spanning\n"); + mTableFrame->Dump(false, true, false); +#endif +} + +void BasicTableLayoutStrategy::ComputeIntrinsicISizes( + gfxContext* aRenderingContext) { + ComputeColumnIntrinsicISizes(aRenderingContext); + + nsTableCellMap* cellMap = mTableFrame->GetCellMap(); + nscoord min = 0, pref = 0, max_small_pct_pref = 0, nonpct_pref_total = 0; + float pct_total = 0.0f; // always from 0.0f - 1.0f + int32_t colCount = cellMap->GetColCount(); + // add a total of (colcount + 1) lots of cellSpacingX for columns where a + // cell originates + nscoord add = mTableFrame->GetColSpacing(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; + } + if (mTableFrame->ColumnHasCellSpacingBefore(col)) { + add += mTableFrame->GetColSpacing(col - 1); + } + min += colFrame->GetMinCoord(); + pref = NSCoordSaturatingAdd(pref, colFrame->GetPrefCoord()); + + // Percentages are of the table, so we have to reverse them for + // intrinsic isizes. + float p = colFrame->GetPrefPercent(); + if (p > 0.0f) { + nscoord colPref = colFrame->GetPrefCoord(); + nscoord new_small_pct_expand = + (colPref == nscoord_MAX ? nscoord_MAX : nscoord(float(colPref) / p)); + if (new_small_pct_expand > max_small_pct_pref) { + max_small_pct_pref = new_small_pct_expand; + } + pct_total += p; + } else { + nonpct_pref_total = + NSCoordSaturatingAdd(nonpct_pref_total, colFrame->GetPrefCoord()); + } + } + + nscoord pref_pct_expand = pref; + + // Account for small percentages expanding the preferred isize of + // *other* columns. + if (max_small_pct_pref > pref_pct_expand) { + pref_pct_expand = max_small_pct_pref; + } + + // Account for large percentages expanding the preferred isize of + // themselves. There's no need to iterate over the columns multiple + // times, since when there is such a need, the small percentage + // effect is bigger anyway. (I think!) + NS_ASSERTION(0.0f <= pct_total && pct_total <= 1.0f, + "column percentage inline-sizes not adjusted down to 100%"); + if (pct_total == 1.0f) { + if (nonpct_pref_total > 0) { + pref_pct_expand = nscoord_MAX; + // XXX Or should I use some smaller value? (Test this using + // nested tables!) + } + } else { + nscoord large_pct_pref = + (nonpct_pref_total == nscoord_MAX + ? nscoord_MAX + : nscoord(float(nonpct_pref_total) / (1.0f - pct_total))); + if (large_pct_pref > pref_pct_expand) pref_pct_expand = large_pct_pref; + } + + // border-spacing isn't part of the basis for percentages + if (colCount > 0) { + min += add; + pref = NSCoordSaturatingAdd(pref, add); + pref_pct_expand = NSCoordSaturatingAdd(pref_pct_expand, add); + } + + mMinISize = min; + mPrefISize = pref; + mPrefISizePctExpand = pref_pct_expand; +} + +/* virtual */ +void BasicTableLayoutStrategy::MarkIntrinsicISizesDirty() { + mMinISize = NS_INTRINSIC_ISIZE_UNKNOWN; + mPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN; + mPrefISizePctExpand = NS_INTRINSIC_ISIZE_UNKNOWN; + mLastCalcISize = nscoord_MIN; +} + +/* virtual */ +void BasicTableLayoutStrategy::ComputeColumnISizes( + const ReflowInput& aReflowInput) { + nscoord iSize = aReflowInput.ComputedISize(); + + if (mLastCalcISize == iSize) { + return; + } + mLastCalcISize = iSize; + + NS_ASSERTION((mMinISize == NS_INTRINSIC_ISIZE_UNKNOWN) == + (mPrefISize == NS_INTRINSIC_ISIZE_UNKNOWN), + "dirtyness out of sync"); + NS_ASSERTION((mMinISize == NS_INTRINSIC_ISIZE_UNKNOWN) == + (mPrefISizePctExpand == NS_INTRINSIC_ISIZE_UNKNOWN), + "dirtyness out of sync"); + // XXX Is this needed? + if (mMinISize == NS_INTRINSIC_ISIZE_UNKNOWN) { + ComputeIntrinsicISizes(aReflowInput.mRenderingContext); + } + + nsTableCellMap* cellMap = mTableFrame->GetCellMap(); + int32_t colCount = cellMap->GetColCount(); + if (colCount <= 0) return; // nothing to do + + DistributeISizeToColumns(iSize, 0, colCount, BTLS_FINAL_ISIZE, false); + +#ifdef DEBUG_TABLE_STRATEGY + printf("ComputeColumnISizes final\n"); + mTableFrame->Dump(false, true, false); +#endif +} + +void BasicTableLayoutStrategy::DistributePctISizeToColumns(float aSpanPrefPct, + int32_t aFirstCol, + int32_t aColCount) { + // First loop to determine: + int32_t nonPctColCount = 0; // number of spanned columns without % isize + nscoord nonPctTotalPrefISize = 0; // total pref isize of those columns + // and to reduce aSpanPrefPct by columns that already have % isize + + int32_t scol, scol_end; + nsTableCellMap* cellMap = mTableFrame->GetCellMap(); + for (scol = aFirstCol, scol_end = aFirstCol + aColCount; scol < scol_end; + ++scol) { + nsTableColFrame* scolFrame = mTableFrame->GetColFrame(scol); + if (!scolFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + float scolPct = scolFrame->GetPrefPercent(); + if (scolPct == 0.0f) { + nonPctTotalPrefISize += scolFrame->GetPrefCoord(); + if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) { + ++nonPctColCount; + } + } else { + aSpanPrefPct -= scolPct; + } + } + + if (aSpanPrefPct <= 0.0f || nonPctColCount == 0) { + // There's no %-isize on the colspan left over to distribute, + // or there are no columns to which we could distribute %-isize + return; + } + + // Second loop, to distribute what remains of aSpanPrefPct + // between the non-percent-isize spanned columns + const bool spanHasNonPctPref = nonPctTotalPrefISize > 0; // Loop invariant + for (scol = aFirstCol, scol_end = aFirstCol + aColCount; scol < scol_end; + ++scol) { + nsTableColFrame* scolFrame = mTableFrame->GetColFrame(scol); + if (!scolFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + + if (scolFrame->GetPrefPercent() == 0.0f) { + NS_ASSERTION((!spanHasNonPctPref || nonPctTotalPrefISize != 0) && + nonPctColCount != 0, + "should not be zero if we haven't allocated " + "all pref percent"); + + float allocatedPct; // % isize to be given to this column + if (spanHasNonPctPref) { + // Group so we're multiplying by 1.0f when we need + // to use up aSpanPrefPct. + allocatedPct = aSpanPrefPct * (float(scolFrame->GetPrefCoord()) / + float(nonPctTotalPrefISize)); + } else if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) { + // distribute equally when all pref isizes are 0 + allocatedPct = aSpanPrefPct / float(nonPctColCount); + } else { + allocatedPct = 0.0f; + } + // Allocate the percent + scolFrame->AddSpanPrefPercent(allocatedPct); + + // To avoid accumulating rounding error from division, + // subtract this column's values from the totals. + aSpanPrefPct -= allocatedPct; + nonPctTotalPrefISize -= scolFrame->GetPrefCoord(); + if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) { + --nonPctColCount; + } + + if (!aSpanPrefPct) { + // No more span-percent-isize to distribute --> we're done. + NS_ASSERTION( + spanHasNonPctPref ? nonPctTotalPrefISize == 0 : nonPctColCount == 0, + "No more pct inline-size to distribute, " + "but there are still cols that need some."); + return; + } + } + } +} + +#ifdef DEBUG +// Bypass some assertions for tables inside XUL which we're realistically not +// going to investigate unless they cause havoc. Thunderbird hits these very +// often. +static bool IsCloseToXULBox(nsTableFrame* aTableFrame) { + // NOTE: GetParent() is guaranteed to be the table wrapper (thus non-null). + nsIFrame* f = aTableFrame->GetParent()->GetParent(); + for (size_t i = 0; f && i < 2; ++i) { + if (f->IsXULBoxFrame()) { + return true; + } + f = f->GetParent(); + } + return false; +} +#endif + +void BasicTableLayoutStrategy::DistributeISizeToColumns( + nscoord aISize, int32_t aFirstCol, int32_t aColCount, + BtlsISizeType aISizeType, bool aSpanHasSpecifiedISize) { + NS_ASSERTION( + aISizeType != BTLS_FINAL_ISIZE || + (aFirstCol == 0 && + aColCount == mTableFrame->GetCellMap()->GetColCount()), + "Computing final column isizes, but didn't get full column range"); + + nscoord subtract = 0; + // aISize initially includes border-spacing for the boundaries in between + // each of the columns. We start at aFirstCol + 1 because the first + // in-between boundary would be at the left edge of column aFirstCol + 1 + for (int32_t col = aFirstCol + 1; col < aFirstCol + aColCount; ++col) { + if (mTableFrame->ColumnHasCellSpacingBefore(col)) { + // border-spacing isn't part of the basis for percentages. + subtract += mTableFrame->GetColSpacing(col - 1); + } + } + if (aISizeType == BTLS_FINAL_ISIZE) { + // If we're computing final col-isize, then aISize initially includes + // border spacing on the table's far istart + far iend edge, too. Need + // to subtract those out, too. + subtract += (mTableFrame->GetColSpacing(-1) + + mTableFrame->GetColSpacing(aColCount)); + } + aISize = NSCoordSaturatingSubtract(aISize, subtract, nscoord_MAX); + + /* + * The goal of this function is to distribute |aISize| between the + * columns by making an appropriate AddSpanCoords or SetFinalISize + * call for each column. (We call AddSpanCoords if we're + * distributing a column-spanning cell's minimum or preferred isize + * to its spanned columns. We call SetFinalISize if we're + * distributing a table's final isize to its columns.) + * + * The idea is to either assign one of the following sets of isizes + * or a weighted average of two adjacent sets of isizes. It is not + * possible to assign values smaller than the smallest set of + * isizes. However, see below for handling the case of assigning + * values larger than the largest set of isizes. From smallest to + * largest, these are: + * + * 1. [guess_min] Assign all columns their min isize. + * + * 2. [guess_min_pct] Assign all columns with percentage isizes + * their percentage isize, and all other columns their min isize. + * + * 3. [guess_min_spec] Assign all columns with percentage isizes + * their percentage isize, all columns with specified coordinate + * isizes their pref isize (since it doesn't matter whether it's the + * largest contributor to the pref isize that was the specified + * contributor), and all other columns their min isize. + * + * 4. [guess_pref] Assign all columns with percentage isizes their + * specified isize, and all other columns their pref isize. + * + * If |aISize| is *larger* than what we would assign in (4), then we + * expand the columns: + * + * a. if any columns without a specified coordinate isize or + * percent isize have nonzero pref isize, in proportion to pref + * isize [total_flex_pref] + * + * b. otherwise, if any columns without a specified coordinate + * isize or percent isize, but with cells originating in them, + * have zero pref isize, equally between these + * [numNonSpecZeroISizeCols] + * + * c. otherwise, if any columns without percent isize have nonzero + * pref isize, in proportion to pref isize [total_fixed_pref] + * + * d. otherwise, if any columns have nonzero percentage isizes, in + * proportion to the percentage isizes [total_pct] + * + * e. otherwise, equally. + */ + + // Loop #1 over the columns, to figure out the four values above so + // we know which case we're dealing with. + + nscoord guess_min = 0, guess_min_pct = 0, guess_min_spec = 0, guess_pref = 0, + total_flex_pref = 0, total_fixed_pref = 0; + float total_pct = 0.0f; // 0.0f to 1.0f + int32_t numInfiniteISizeCols = 0; + int32_t numNonSpecZeroISizeCols = 0; + + int32_t col; + nsTableCellMap* cellMap = mTableFrame->GetCellMap(); + for (col = aFirstCol; col < aFirstCol + aColCount; ++col) { + nsTableColFrame* colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + nscoord min_iSize = colFrame->GetMinCoord(); + guess_min += min_iSize; + if (colFrame->GetPrefPercent() != 0.0f) { + float pct = colFrame->GetPrefPercent(); + total_pct += pct; + nscoord val = nscoord(float(aISize) * pct); + if (val < min_iSize) { + val = min_iSize; + } + guess_min_pct = NSCoordSaturatingAdd(guess_min_pct, val); + guess_pref = NSCoordSaturatingAdd(guess_pref, val); + } else { + nscoord pref_iSize = colFrame->GetPrefCoord(); + if (pref_iSize == nscoord_MAX) { + ++numInfiniteISizeCols; + } + guess_pref = NSCoordSaturatingAdd(guess_pref, pref_iSize); + guess_min_pct = NSCoordSaturatingAdd(guess_min_pct, min_iSize); + if (colFrame->GetHasSpecifiedCoord()) { + // we'll add on the rest of guess_min_spec outside the + // loop + nscoord delta = NSCoordSaturatingSubtract(pref_iSize, min_iSize, 0); + guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, delta); + total_fixed_pref = NSCoordSaturatingAdd(total_fixed_pref, pref_iSize); + } else if (pref_iSize == 0) { + if (cellMap->GetNumCellsOriginatingInCol(col) > 0) { + ++numNonSpecZeroISizeCols; + } + } else { + total_flex_pref = NSCoordSaturatingAdd(total_flex_pref, pref_iSize); + } + } + } + guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, guess_min_pct); + + // Determine what we're flexing: + enum Loop2Type { + FLEX_PCT_SMALL, // between (1) and (2) above + FLEX_FIXED_SMALL, // between (2) and (3) above + FLEX_FLEX_SMALL, // between (3) and (4) above + FLEX_FLEX_LARGE, // greater than (4) above, case (a) + FLEX_FLEX_LARGE_ZERO, // greater than (4) above, case (b) + FLEX_FIXED_LARGE, // greater than (4) above, case (c) + FLEX_PCT_LARGE, // greater than (4) above, case (d) + FLEX_ALL_LARGE // greater than (4) above, case (e) + }; + + Loop2Type l2t; + // These are constants (over columns) for each case's math. We use + // a pair of nscoords rather than a float so that we can subtract + // each column's allocation so we avoid accumulating rounding error. + nscoord space; // the amount of extra isize to allocate + union { + nscoord c; + float f; + } basis; // the sum of the statistic over columns to divide it + if (aISize < guess_pref) { + if (aISizeType != BTLS_FINAL_ISIZE && aISize <= guess_min) { + // Return early -- we don't have any extra space to distribute. + return; + } + NS_ASSERTION(!(aISizeType == BTLS_FINAL_ISIZE && aISize < guess_min) || + IsCloseToXULBox(mTableFrame), + "Table inline-size is less than the " + "sum of its columns' min inline-sizes"); + if (aISize < guess_min_pct) { + l2t = FLEX_PCT_SMALL; + space = aISize - guess_min; + basis.c = guess_min_pct - guess_min; + } else if (aISize < guess_min_spec) { + l2t = FLEX_FIXED_SMALL; + space = aISize - guess_min_pct; + basis.c = + NSCoordSaturatingSubtract(guess_min_spec, guess_min_pct, nscoord_MAX); + } else { + l2t = FLEX_FLEX_SMALL; + space = aISize - guess_min_spec; + basis.c = + NSCoordSaturatingSubtract(guess_pref, guess_min_spec, nscoord_MAX); + } + } else { + space = NSCoordSaturatingSubtract(aISize, guess_pref, nscoord_MAX); + if (total_flex_pref > 0) { + l2t = FLEX_FLEX_LARGE; + basis.c = total_flex_pref; + } else if (numNonSpecZeroISizeCols > 0) { + l2t = FLEX_FLEX_LARGE_ZERO; + basis.c = numNonSpecZeroISizeCols; + } else if (total_fixed_pref > 0) { + l2t = FLEX_FIXED_LARGE; + basis.c = total_fixed_pref; + } else if (total_pct > 0.0f) { + l2t = FLEX_PCT_LARGE; + basis.f = total_pct; + } else { + l2t = FLEX_ALL_LARGE; + basis.c = aColCount; + } + } + +#ifdef DEBUG_dbaron_off + printf( + "ComputeColumnISizes: %d columns in isize %d,\n" + " guesses=[%d,%d,%d,%d], totals=[%d,%d,%f],\n" + " l2t=%d, space=%d, basis.c=%d\n", + aColCount, aISize, guess_min, guess_min_pct, guess_min_spec, guess_pref, + total_flex_pref, total_fixed_pref, total_pct, l2t, space, basis.c); +#endif + + for (col = aFirstCol; col < aFirstCol + aColCount; ++col) { + nsTableColFrame* colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + nscoord col_iSize; + + float pct = colFrame->GetPrefPercent(); + if (pct != 0.0f) { + col_iSize = nscoord(float(aISize) * pct); + nscoord col_min = colFrame->GetMinCoord(); + if (col_iSize < col_min) { + col_iSize = col_min; + } + } else { + col_iSize = colFrame->GetPrefCoord(); + } + + nscoord col_iSize_before_adjust = col_iSize; + + switch (l2t) { + case FLEX_PCT_SMALL: + col_iSize = col_iSize_before_adjust = colFrame->GetMinCoord(); + if (pct != 0.0f) { + nscoord pct_minus_min = nscoord(float(aISize) * pct) - col_iSize; + if (pct_minus_min > 0) { + float c = float(space) / float(basis.c); + basis.c -= pct_minus_min; + col_iSize = NSCoordSaturatingAdd( + col_iSize, NSToCoordRound(float(pct_minus_min) * c)); + } + } + break; + case FLEX_FIXED_SMALL: + if (pct == 0.0f) { + NS_ASSERTION(col_iSize == colFrame->GetPrefCoord(), + "wrong inline-size assigned"); + if (colFrame->GetHasSpecifiedCoord()) { + nscoord col_min = colFrame->GetMinCoord(); + nscoord pref_minus_min = col_iSize - col_min; + col_iSize = col_iSize_before_adjust = col_min; + if (pref_minus_min != 0) { + float c = float(space) / float(basis.c); + basis.c -= pref_minus_min; + col_iSize = NSCoordSaturatingAdd( + col_iSize, NSToCoordRound(float(pref_minus_min) * c)); + } + } else + col_iSize = col_iSize_before_adjust = colFrame->GetMinCoord(); + } + break; + case FLEX_FLEX_SMALL: + if (pct == 0.0f && !colFrame->GetHasSpecifiedCoord()) { + NS_ASSERTION(col_iSize == colFrame->GetPrefCoord(), + "wrong inline-size assigned"); + nscoord col_min = colFrame->GetMinCoord(); + nscoord pref_minus_min = + NSCoordSaturatingSubtract(col_iSize, col_min, 0); + col_iSize = col_iSize_before_adjust = col_min; + if (pref_minus_min != 0) { + float c = float(space) / float(basis.c); + // If we have infinite-isize cols, then the standard + // adjustment to col_iSize using 'c' won't work, + // because basis.c and pref_minus_min are both + // nscoord_MAX and will cancel each other out in the + // col_iSize adjustment (making us assign all the + // space to the first inf-isize col). To correct for + // this, we'll also divide by numInfiniteISizeCols to + // spread the space equally among the inf-isize cols. + if (numInfiniteISizeCols) { + if (colFrame->GetPrefCoord() == nscoord_MAX) { + c = c / float(numInfiniteISizeCols); + --numInfiniteISizeCols; + } else { + c = 0.0f; + } + } + basis.c = + NSCoordSaturatingSubtract(basis.c, pref_minus_min, nscoord_MAX); + col_iSize = NSCoordSaturatingAdd( + col_iSize, NSToCoordRound(float(pref_minus_min) * c)); + } + } + break; + case FLEX_FLEX_LARGE: + if (pct == 0.0f && !colFrame->GetHasSpecifiedCoord()) { + NS_ASSERTION(col_iSize == colFrame->GetPrefCoord(), + "wrong inline-size assigned"); + if (col_iSize != 0) { + if (space == nscoord_MAX) { + basis.c -= col_iSize; + col_iSize = nscoord_MAX; + } else { + float c = float(space) / float(basis.c); + basis.c -= col_iSize; + col_iSize = NSCoordSaturatingAdd( + col_iSize, NSToCoordRound(float(col_iSize) * c)); + } + } + } + break; + case FLEX_FLEX_LARGE_ZERO: + if (pct == 0.0f && !colFrame->GetHasSpecifiedCoord() && + cellMap->GetNumCellsOriginatingInCol(col) > 0) { + NS_ASSERTION(col_iSize == 0 && colFrame->GetPrefCoord() == 0, + "Since we're in FLEX_FLEX_LARGE_ZERO case, " + "all auto-inline-size cols should have zero " + "pref inline-size."); + float c = float(space) / float(basis.c); + col_iSize += NSToCoordRound(c); + --basis.c; + } + break; + case FLEX_FIXED_LARGE: + if (pct == 0.0f) { + NS_ASSERTION(col_iSize == colFrame->GetPrefCoord(), + "wrong inline-size assigned"); + NS_ASSERTION( + colFrame->GetHasSpecifiedCoord() || colFrame->GetPrefCoord() == 0, + "wrong case"); + if (col_iSize != 0) { + float c = float(space) / float(basis.c); + basis.c -= col_iSize; + col_iSize = NSCoordSaturatingAdd( + col_iSize, NSToCoordRound(float(col_iSize) * c)); + } + } + break; + case FLEX_PCT_LARGE: + NS_ASSERTION(pct != 0.0f || colFrame->GetPrefCoord() == 0, + "wrong case"); + if (pct != 0.0f) { + float c = float(space) / basis.f; + col_iSize = NSCoordSaturatingAdd(col_iSize, NSToCoordRound(pct * c)); + basis.f -= pct; + } + break; + case FLEX_ALL_LARGE: { + float c = float(space) / float(basis.c); + col_iSize = NSCoordSaturatingAdd(col_iSize, NSToCoordRound(c)); + --basis.c; + } break; + } + + // Only subtract from space if it's a real number. + if (space != nscoord_MAX) { + NS_ASSERTION(col_iSize != nscoord_MAX, + "How is col_iSize nscoord_MAX if space isn't?"); + NS_ASSERTION( + col_iSize_before_adjust != nscoord_MAX, + "How is col_iSize_before_adjust nscoord_MAX if space isn't?"); + space -= col_iSize - col_iSize_before_adjust; + } + + NS_ASSERTION(col_iSize >= colFrame->GetMinCoord(), + "assigned inline-size smaller than min"); + + // Apply the new isize + switch (aISizeType) { + case BTLS_MIN_ISIZE: { + // Note: AddSpanCoords requires both a min and pref isize. + // For the pref isize, we'll just pass in our computed + // min isize, because the real pref isize will be at least + // as big + colFrame->AddSpanCoords(col_iSize, col_iSize, aSpanHasSpecifiedISize); + } break; + case BTLS_PREF_ISIZE: { + // Note: AddSpanCoords requires both a min and pref isize. + // For the min isize, we'll just pass in 0, because + // the real min isize will be at least 0 + colFrame->AddSpanCoords(0, col_iSize, aSpanHasSpecifiedISize); + } break; + case BTLS_FINAL_ISIZE: { + nscoord old_final = colFrame->GetFinalISize(); + colFrame->SetFinalISize(col_iSize); + + if (old_final != col_iSize) { + mTableFrame->DidResizeColumns(); + } + } break; + } + } +#ifdef DEBUG + if (!IsCloseToXULBox(mTableFrame)) { + NS_ASSERTION((space == 0 || space == nscoord_MAX) && + ((l2t == FLEX_PCT_LARGE) + ? (-0.001f < basis.f && basis.f < 0.001f) + : (basis.c == 0 || basis.c == nscoord_MAX)), + "didn't subtract all that we added"); + } +#endif +} |