michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: // vim:cindent:ts=4:et:sw=4: michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * Web-compatible algorithms that determine column and table widths, michael@0: * used for CSS2's 'table-layout: auto'. michael@0: */ michael@0: michael@0: #include "BasicTableLayoutStrategy.h" michael@0: #include "nsTableFrame.h" michael@0: #include "nsTableCellFrame.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "SpanningCellSorter.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::layout; michael@0: michael@0: namespace css = mozilla::css; michael@0: michael@0: #undef DEBUG_TABLE_STRATEGY michael@0: michael@0: BasicTableLayoutStrategy::BasicTableLayoutStrategy(nsTableFrame *aTableFrame) michael@0: : nsITableLayoutStrategy(nsITableLayoutStrategy::Auto) michael@0: , mTableFrame(aTableFrame) michael@0: { michael@0: MarkIntrinsicWidthsDirty(); michael@0: } michael@0: michael@0: /* virtual */ michael@0: BasicTableLayoutStrategy::~BasicTableLayoutStrategy() michael@0: { michael@0: } michael@0: michael@0: /* virtual */ nscoord michael@0: BasicTableLayoutStrategy::GetMinWidth(nsRenderingContext* aRenderingContext) michael@0: { michael@0: DISPLAY_MIN_WIDTH(mTableFrame, mMinWidth); michael@0: if (mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN) michael@0: ComputeIntrinsicWidths(aRenderingContext); michael@0: return mMinWidth; michael@0: } michael@0: michael@0: /* virtual */ nscoord michael@0: BasicTableLayoutStrategy::GetPrefWidth(nsRenderingContext* aRenderingContext, michael@0: bool aComputingSize) michael@0: { michael@0: DISPLAY_PREF_WIDTH(mTableFrame, mPrefWidth); michael@0: NS_ASSERTION((mPrefWidth == NS_INTRINSIC_WIDTH_UNKNOWN) == michael@0: (mPrefWidthPctExpand == NS_INTRINSIC_WIDTH_UNKNOWN), michael@0: "dirtyness out of sync"); michael@0: if (mPrefWidth == NS_INTRINSIC_WIDTH_UNKNOWN) michael@0: ComputeIntrinsicWidths(aRenderingContext); michael@0: return aComputingSize ? mPrefWidthPctExpand : mPrefWidth; michael@0: } michael@0: michael@0: struct CellWidthInfo { michael@0: CellWidthInfo(nscoord aMinCoord, nscoord aPrefCoord, michael@0: float aPrefPercent, bool aHasSpecifiedWidth) michael@0: : hasSpecifiedWidth(aHasSpecifiedWidth) michael@0: , minCoord(aMinCoord) michael@0: , prefCoord(aPrefCoord) michael@0: , prefPercent(aPrefPercent) michael@0: { michael@0: } michael@0: michael@0: bool hasSpecifiedWidth; michael@0: nscoord minCoord; michael@0: nscoord prefCoord; michael@0: float prefPercent; michael@0: }; michael@0: michael@0: // Used for both column and cell calculations. The parts needed only michael@0: // for cells are skipped when aIsCell is false. michael@0: static CellWidthInfo michael@0: GetWidthInfo(nsRenderingContext *aRenderingContext, michael@0: nsIFrame *aFrame, bool aIsCell) michael@0: { michael@0: nscoord minCoord, prefCoord; michael@0: const nsStylePosition *stylePos = aFrame->StylePosition(); michael@0: bool isQuirks = aFrame->PresContext()->CompatibilityMode() == michael@0: eCompatibility_NavQuirks; michael@0: nscoord boxSizingToBorderEdge = 0; michael@0: if (aIsCell) { michael@0: // If aFrame is a container for font size inflation, then shrink michael@0: // wrapping inside of it should not apply font size inflation. michael@0: AutoMaybeDisableFontInflation an(aFrame); michael@0: michael@0: minCoord = aFrame->GetMinWidth(aRenderingContext); michael@0: prefCoord = aFrame->GetPrefWidth(aRenderingContext); michael@0: // Until almost the end of this function, minCoord and prefCoord michael@0: // represent the box-sizing based width values (which mean they michael@0: // should include horizontal padding and border width when michael@0: // box-sizing is set to border-box). michael@0: // Note that this function returns border-box width, we add the michael@0: // outer edges near the end of this function. michael@0: michael@0: // XXX Should we ignore percentage padding? michael@0: nsIFrame::IntrinsicWidthOffsetData offsets = aFrame->IntrinsicWidthOffsets(aRenderingContext); michael@0: michael@0: // In quirks mode, table cell width should be content-box, michael@0: // but height should be border box. michael@0: // Because of this historic anomaly, we do not use quirk.css. michael@0: // (We can't specify one value of box-sizing for width and another michael@0: // for height). michael@0: // For this reason, we also do not use box-sizing for just one of michael@0: // them, as this may be confusing. michael@0: if (isQuirks) { michael@0: boxSizingToBorderEdge = offsets.hPadding + offsets.hBorder; michael@0: } michael@0: else { michael@0: switch (stylePos->mBoxSizing) { michael@0: case NS_STYLE_BOX_SIZING_CONTENT: michael@0: boxSizingToBorderEdge = offsets.hPadding + offsets.hBorder; michael@0: break; michael@0: case NS_STYLE_BOX_SIZING_PADDING: michael@0: minCoord += offsets.hPadding; michael@0: prefCoord += offsets.hPadding; michael@0: boxSizingToBorderEdge = offsets.hBorder; michael@0: break; michael@0: default: michael@0: // NS_STYLE_BOX_SIZING_BORDER michael@0: minCoord += offsets.hPadding + offsets.hBorder; michael@0: prefCoord += offsets.hPadding + offsets.hBorder; michael@0: break; michael@0: } michael@0: } michael@0: } else { michael@0: minCoord = 0; michael@0: prefCoord = 0; michael@0: } michael@0: float prefPercent = 0.0f; michael@0: bool hasSpecifiedWidth = false; michael@0: michael@0: const nsStyleCoord &width = stylePos->mWidth; michael@0: nsStyleUnit unit = width.GetUnit(); michael@0: // NOTE: We're ignoring calc() units with percentages here, for lack of a michael@0: // sensible idea for what to do with them. This means calc() with michael@0: // percentages is basically handled like 'auto' for table cells and michael@0: // columns. michael@0: if (width.ConvertsToLength()) { michael@0: hasSpecifiedWidth = true; michael@0: // Note: since ComputeWidthValue was designed to return content-box michael@0: // width, it will (in some cases) subtract the box-sizing edges. michael@0: // We prevent this unwanted behavior by calling it with michael@0: // aContentEdgeToBoxSizing and aBoxSizingToMarginEdge set to 0. michael@0: nscoord w = nsLayoutUtils::ComputeWidthValue(aRenderingContext, michael@0: aFrame, 0, 0, 0, width); michael@0: // Quirk: A cell with "nowrap" set and a coord value for the michael@0: // width which is bigger than the intrinsic minimum width uses michael@0: // that coord value as the minimum width. michael@0: // This is kept up-to-date with dynamic changes to nowrap by code in michael@0: // nsTableCellFrame::AttributeChanged michael@0: if (aIsCell && w > minCoord && isQuirks && michael@0: aFrame->GetContent()->HasAttr(kNameSpaceID_None, michael@0: nsGkAtoms::nowrap)) { michael@0: minCoord = w; michael@0: } michael@0: prefCoord = std::max(w, minCoord); michael@0: } else if (unit == eStyleUnit_Percent) { michael@0: prefPercent = width.GetPercentValue(); michael@0: } else if (unit == eStyleUnit_Enumerated && aIsCell) { michael@0: switch (width.GetIntValue()) { michael@0: case NS_STYLE_WIDTH_MAX_CONTENT: michael@0: // 'width' only affects pref width, not min michael@0: // width, so don't change anything michael@0: break; michael@0: case NS_STYLE_WIDTH_MIN_CONTENT: michael@0: prefCoord = minCoord; michael@0: break; michael@0: case NS_STYLE_WIDTH_FIT_CONTENT: michael@0: case NS_STYLE_WIDTH_AVAILABLE: michael@0: // act just like 'width: auto' michael@0: break; michael@0: default: michael@0: NS_NOTREACHED("unexpected enumerated value"); michael@0: } michael@0: } michael@0: michael@0: nsStyleCoord maxWidth(stylePos->mMaxWidth); michael@0: if (maxWidth.GetUnit() == eStyleUnit_Enumerated) { michael@0: if (!aIsCell || maxWidth.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE) michael@0: maxWidth.SetNoneValue(); michael@0: else if (maxWidth.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT) michael@0: // for 'max-width', '-moz-fit-content' is like michael@0: // '-moz-max-content' michael@0: maxWidth.SetIntValue(NS_STYLE_WIDTH_MAX_CONTENT, michael@0: eStyleUnit_Enumerated); michael@0: } michael@0: unit = maxWidth.GetUnit(); michael@0: // XXX To really implement 'max-width' well, we'd need to store michael@0: // it separately on the columns. michael@0: if (maxWidth.ConvertsToLength() || unit == eStyleUnit_Enumerated) { michael@0: nscoord w = michael@0: nsLayoutUtils::ComputeWidthValue(aRenderingContext, aFrame, michael@0: 0, 0, 0, maxWidth); michael@0: if (w < minCoord) michael@0: minCoord = w; michael@0: if (w < prefCoord) michael@0: prefCoord = w; michael@0: } else if (unit == eStyleUnit_Percent) { michael@0: float p = stylePos->mMaxWidth.GetPercentValue(); michael@0: if (p < prefPercent) michael@0: prefPercent = p; michael@0: } michael@0: // treat calc() with percentages on max-width just like 'none'. michael@0: michael@0: nsStyleCoord minWidth(stylePos->mMinWidth); michael@0: if (minWidth.GetUnit() == eStyleUnit_Enumerated) { michael@0: if (!aIsCell || minWidth.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE) michael@0: minWidth.SetCoordValue(0); michael@0: else if (minWidth.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT) michael@0: // for 'min-width', '-moz-fit-content' is like michael@0: // '-moz-min-content' michael@0: minWidth.SetIntValue(NS_STYLE_WIDTH_MIN_CONTENT, michael@0: eStyleUnit_Enumerated); michael@0: } michael@0: unit = minWidth.GetUnit(); michael@0: if (minWidth.ConvertsToLength() || unit == eStyleUnit_Enumerated) { michael@0: nscoord w = michael@0: nsLayoutUtils::ComputeWidthValue(aRenderingContext, aFrame, michael@0: 0, 0, 0, minWidth); michael@0: if (w > minCoord) michael@0: minCoord = w; michael@0: if (w > prefCoord) michael@0: prefCoord = w; michael@0: } else if (unit == eStyleUnit_Percent) { michael@0: float p = stylePos->mMinWidth.GetPercentValue(); michael@0: if (p > prefPercent) michael@0: prefPercent = p; michael@0: } michael@0: // treat calc() with percentages on min-width just like '0'. michael@0: michael@0: // XXX Should col frame have border/padding considered? michael@0: if (aIsCell) { michael@0: minCoord += boxSizingToBorderEdge; michael@0: prefCoord = NSCoordSaturatingAdd(prefCoord, boxSizingToBorderEdge); michael@0: } michael@0: michael@0: return CellWidthInfo(minCoord, prefCoord, prefPercent, hasSpecifiedWidth); michael@0: } michael@0: michael@0: static inline CellWidthInfo michael@0: GetCellWidthInfo(nsRenderingContext *aRenderingContext, michael@0: nsTableCellFrame *aCellFrame) michael@0: { michael@0: return GetWidthInfo(aRenderingContext, aCellFrame, true); michael@0: } michael@0: michael@0: static inline CellWidthInfo michael@0: GetColWidthInfo(nsRenderingContext *aRenderingContext, michael@0: nsIFrame *aFrame) michael@0: { michael@0: return GetWidthInfo(aRenderingContext, aFrame, false); michael@0: } michael@0: michael@0: michael@0: /** michael@0: * The algorithm in this function, in addition to meeting the michael@0: * requirements of Web-compatibility, is also invariant under reordering michael@0: * of the rows within a table (something that most, but not all, other michael@0: * browsers are). michael@0: */ michael@0: void michael@0: BasicTableLayoutStrategy::ComputeColumnIntrinsicWidths(nsRenderingContext* aRenderingContext) michael@0: { michael@0: nsTableFrame *tableFrame = mTableFrame; michael@0: nsTableCellMap *cellMap = tableFrame->GetCellMap(); michael@0: michael@0: mozilla::AutoStackArena arena; michael@0: SpanningCellSorter spanningCells; michael@0: michael@0: // Loop over the columns to consider the columns and cells *without* michael@0: // a colspan. michael@0: int32_t col, col_end; michael@0: for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) { michael@0: nsTableColFrame *colFrame = tableFrame->GetColFrame(col); michael@0: if (!colFrame) { michael@0: NS_ERROR("column frames out of sync with cell map"); michael@0: continue; michael@0: } michael@0: colFrame->ResetIntrinsics(); michael@0: colFrame->ResetSpanIntrinsics(); michael@0: michael@0: // Consider the widths on the column. michael@0: CellWidthInfo colInfo = GetColWidthInfo(aRenderingContext, colFrame); michael@0: colFrame->AddCoords(colInfo.minCoord, colInfo.prefCoord, michael@0: colInfo.hasSpecifiedWidth); michael@0: colFrame->AddPrefPercent(colInfo.prefPercent); michael@0: michael@0: // Consider the widths on the column-group. Note that we follow michael@0: // what the HTML spec says here, and make the width apply to michael@0: // each column in the group, not the group as a whole. michael@0: michael@0: // If column has width, column-group doesn't override width. michael@0: if (colInfo.minCoord == 0 && colInfo.prefCoord == 0 && michael@0: colInfo.prefPercent == 0.0f) { michael@0: NS_ASSERTION(colFrame->GetParent()->GetType() == michael@0: nsGkAtoms::tableColGroupFrame, michael@0: "expected a column-group"); michael@0: colInfo = GetColWidthInfo(aRenderingContext, colFrame->GetParent()); michael@0: colFrame->AddCoords(colInfo.minCoord, colInfo.prefCoord, michael@0: colInfo.hasSpecifiedWidth); michael@0: colFrame->AddPrefPercent(colInfo.prefPercent); michael@0: } michael@0: michael@0: // Consider the contents of and the widths on the cells without michael@0: // colspans. michael@0: nsCellMapColumnIterator columnIter(cellMap, col); michael@0: int32_t row, colSpan; michael@0: nsTableCellFrame* cellFrame; michael@0: while ((cellFrame = columnIter.GetNextFrame(&row, &colSpan))) { michael@0: if (colSpan > 1) { michael@0: spanningCells.AddCell(colSpan, row, col); michael@0: continue; michael@0: } michael@0: michael@0: CellWidthInfo info = GetCellWidthInfo(aRenderingContext, cellFrame); michael@0: michael@0: colFrame->AddCoords(info.minCoord, info.prefCoord, michael@0: info.hasSpecifiedWidth); michael@0: colFrame->AddPrefPercent(info.prefPercent); michael@0: } michael@0: #ifdef DEBUG_dbaron_off michael@0: printf("table %p col %d nonspan: min=%d pref=%d spec=%d pct=%f\n", michael@0: mTableFrame, col, colFrame->GetMinCoord(), michael@0: colFrame->GetPrefCoord(), colFrame->GetHasSpecifiedCoord(), michael@0: colFrame->GetPrefPercent()); michael@0: #endif michael@0: } michael@0: #ifdef DEBUG_TABLE_STRATEGY michael@0: printf("ComputeColumnIntrinsicWidths single\n"); michael@0: mTableFrame->Dump(false, true, false); michael@0: #endif michael@0: michael@0: // Consider the cells with a colspan that we saved in the loop above michael@0: // into the spanning cell sorter. We consider these cells by seeing michael@0: // if they require adding to the widths resulting only from cells michael@0: // with a smaller colspan, and therefore we must process them sorted michael@0: // in increasing order by colspan. For each colspan group, we michael@0: // accumulate new values to accumulate in the column frame's Span* michael@0: // members. michael@0: // michael@0: // Considering things only relative to the widths resulting from michael@0: // cells with smaller colspans (rather than incrementally including michael@0: // the results from spanning cells, or doing spanning and michael@0: // non-spanning cells in a single pass) means that layout remains michael@0: // row-order-invariant and (except for percentage widths that add to michael@0: // more than 100%) column-order invariant. michael@0: // michael@0: // Starting with smaller colspans makes it more likely that we michael@0: // satisfy all the constraints given and don't distribute space to michael@0: // columns where we don't need it. michael@0: SpanningCellSorter::Item *item; michael@0: int32_t colSpan; michael@0: while ((item = spanningCells.GetNext(&colSpan))) { michael@0: NS_ASSERTION(colSpan > 1, michael@0: "cell should not have been put in spanning cell sorter"); michael@0: do { michael@0: int32_t row = item->row; michael@0: col = item->col; michael@0: CellData *cellData = cellMap->GetDataAt(row, col); michael@0: NS_ASSERTION(cellData && cellData->IsOrig(), michael@0: "bogus result from spanning cell sorter"); michael@0: michael@0: nsTableCellFrame *cellFrame = cellData->GetCellFrame(); michael@0: NS_ASSERTION(cellFrame, "bogus result from spanning cell sorter"); michael@0: michael@0: CellWidthInfo info = GetCellWidthInfo(aRenderingContext, cellFrame); michael@0: michael@0: if (info.prefPercent > 0.0f) { michael@0: DistributePctWidthToColumns(info.prefPercent, michael@0: col, colSpan); michael@0: } michael@0: DistributeWidthToColumns(info.minCoord, col, colSpan, michael@0: BTLS_MIN_WIDTH, info.hasSpecifiedWidth); michael@0: DistributeWidthToColumns(info.prefCoord, col, colSpan, michael@0: BTLS_PREF_WIDTH, info.hasSpecifiedWidth); michael@0: } while ((item = item->next)); michael@0: michael@0: // Combine the results of the span analysis into the main results, michael@0: // for each increment of colspan. michael@0: michael@0: for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) { michael@0: nsTableColFrame *colFrame = tableFrame->GetColFrame(col); michael@0: if (!colFrame) { michael@0: NS_ERROR("column frames out of sync with cell map"); michael@0: continue; michael@0: } michael@0: michael@0: colFrame->AccumulateSpanIntrinsics(); michael@0: colFrame->ResetSpanIntrinsics(); michael@0: michael@0: #ifdef DEBUG_dbaron_off michael@0: printf("table %p col %d span %d: min=%d pref=%d spec=%d pct=%f\n", michael@0: mTableFrame, col, colSpan, colFrame->GetMinCoord(), michael@0: colFrame->GetPrefCoord(), colFrame->GetHasSpecifiedCoord(), michael@0: colFrame->GetPrefPercent()); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: // Prevent percentages from adding to more than 100% by (to be michael@0: // compatible with other browsers) treating any percentages that would michael@0: // increase the total percentage to more than 100% as the number that michael@0: // would increase it to only 100% (which is 0% if we've already hit michael@0: // 100%). This means layout depends on the order of columns. michael@0: float pct_used = 0.0f; michael@0: for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) { michael@0: nsTableColFrame *colFrame = tableFrame->GetColFrame(col); michael@0: if (!colFrame) { michael@0: NS_ERROR("column frames out of sync with cell map"); michael@0: continue; michael@0: } michael@0: michael@0: colFrame->AdjustPrefPercent(&pct_used); michael@0: } michael@0: michael@0: #ifdef DEBUG_TABLE_STRATEGY michael@0: printf("ComputeColumnIntrinsicWidths spanning\n"); michael@0: mTableFrame->Dump(false, true, false); michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: BasicTableLayoutStrategy::ComputeIntrinsicWidths(nsRenderingContext* aRenderingContext) michael@0: { michael@0: ComputeColumnIntrinsicWidths(aRenderingContext); michael@0: michael@0: nsTableCellMap *cellMap = mTableFrame->GetCellMap(); michael@0: nscoord min = 0, pref = 0, max_small_pct_pref = 0, nonpct_pref_total = 0; michael@0: float pct_total = 0.0f; // always from 0.0f - 1.0f michael@0: int32_t colCount = cellMap->GetColCount(); michael@0: nscoord spacing = mTableFrame->GetCellSpacingX(); michael@0: nscoord add = spacing; // add (colcount + 1) * spacing for columns michael@0: // where a cell originates michael@0: michael@0: for (int32_t col = 0; col < colCount; ++col) { michael@0: nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); michael@0: if (!colFrame) { michael@0: NS_ERROR("column frames out of sync with cell map"); michael@0: continue; michael@0: } michael@0: if (mTableFrame->ColumnHasCellSpacingBefore(col)) { michael@0: add += spacing; michael@0: } michael@0: min += colFrame->GetMinCoord(); michael@0: pref = NSCoordSaturatingAdd(pref, colFrame->GetPrefCoord()); michael@0: michael@0: // Percentages are of the table, so we have to reverse them for michael@0: // intrinsic widths. michael@0: float p = colFrame->GetPrefPercent(); michael@0: if (p > 0.0f) { michael@0: nscoord colPref = colFrame->GetPrefCoord(); michael@0: nscoord new_small_pct_expand = michael@0: (colPref == nscoord_MAX ? michael@0: nscoord_MAX : nscoord(float(colPref) / p)); michael@0: if (new_small_pct_expand > max_small_pct_pref) { michael@0: max_small_pct_pref = new_small_pct_expand; michael@0: } michael@0: pct_total += p; michael@0: } else { michael@0: nonpct_pref_total = NSCoordSaturatingAdd(nonpct_pref_total, michael@0: colFrame->GetPrefCoord()); michael@0: } michael@0: } michael@0: michael@0: nscoord pref_pct_expand = pref; michael@0: michael@0: // Account for small percentages expanding the preferred width of michael@0: // *other* columns. michael@0: if (max_small_pct_pref > pref_pct_expand) { michael@0: pref_pct_expand = max_small_pct_pref; michael@0: } michael@0: michael@0: // Account for large percentages expanding the preferred width of michael@0: // themselves. There's no need to iterate over the columns multiple michael@0: // times, since when there is such a need, the small percentage michael@0: // effect is bigger anyway. (I think!) michael@0: NS_ASSERTION(0.0f <= pct_total && pct_total <= 1.0f, michael@0: "column percentage widths not adjusted down to 100%"); michael@0: if (pct_total == 1.0f) { michael@0: if (nonpct_pref_total > 0) { michael@0: pref_pct_expand = nscoord_MAX; michael@0: // XXX Or should I use some smaller value? (Test this using michael@0: // nested tables!) michael@0: } michael@0: } else { michael@0: nscoord large_pct_pref = michael@0: (nonpct_pref_total == nscoord_MAX ? michael@0: nscoord_MAX : michael@0: nscoord(float(nonpct_pref_total) / (1.0f - pct_total))); michael@0: if (large_pct_pref > pref_pct_expand) michael@0: pref_pct_expand = large_pct_pref; michael@0: } michael@0: michael@0: // border-spacing isn't part of the basis for percentages michael@0: if (colCount > 0) { michael@0: min += add; michael@0: pref = NSCoordSaturatingAdd(pref, add); michael@0: pref_pct_expand = NSCoordSaturatingAdd(pref_pct_expand, add); michael@0: } michael@0: michael@0: mMinWidth = min; michael@0: mPrefWidth = pref; michael@0: mPrefWidthPctExpand = pref_pct_expand; michael@0: } michael@0: michael@0: /* virtual */ void michael@0: BasicTableLayoutStrategy::MarkIntrinsicWidthsDirty() michael@0: { michael@0: mMinWidth = NS_INTRINSIC_WIDTH_UNKNOWN; michael@0: mPrefWidth = NS_INTRINSIC_WIDTH_UNKNOWN; michael@0: mPrefWidthPctExpand = NS_INTRINSIC_WIDTH_UNKNOWN; michael@0: mLastCalcWidth = nscoord_MIN; michael@0: } michael@0: michael@0: /* virtual */ void michael@0: BasicTableLayoutStrategy::ComputeColumnWidths(const nsHTMLReflowState& aReflowState) michael@0: { michael@0: nscoord width = aReflowState.ComputedWidth(); michael@0: michael@0: if (mLastCalcWidth == width) michael@0: return; michael@0: mLastCalcWidth = width; michael@0: michael@0: NS_ASSERTION((mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN) == michael@0: (mPrefWidth == NS_INTRINSIC_WIDTH_UNKNOWN), michael@0: "dirtyness out of sync"); michael@0: NS_ASSERTION((mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN) == michael@0: (mPrefWidthPctExpand == NS_INTRINSIC_WIDTH_UNKNOWN), michael@0: "dirtyness out of sync"); michael@0: // XXX Is this needed? michael@0: if (mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN) michael@0: ComputeIntrinsicWidths(aReflowState.rendContext); michael@0: michael@0: nsTableCellMap *cellMap = mTableFrame->GetCellMap(); michael@0: int32_t colCount = cellMap->GetColCount(); michael@0: if (colCount <= 0) michael@0: return; // nothing to do michael@0: michael@0: DistributeWidthToColumns(width, 0, colCount, BTLS_FINAL_WIDTH, false); michael@0: michael@0: #ifdef DEBUG_TABLE_STRATEGY michael@0: printf("ComputeColumnWidths final\n"); michael@0: mTableFrame->Dump(false, true, false); michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: BasicTableLayoutStrategy::DistributePctWidthToColumns(float aSpanPrefPct, michael@0: int32_t aFirstCol, michael@0: int32_t aColCount) michael@0: { michael@0: // First loop to determine: michael@0: int32_t nonPctColCount = 0; // number of spanned columns without % width michael@0: nscoord nonPctTotalPrefWidth = 0; // total pref width of those columns michael@0: // and to reduce aSpanPrefPct by columns that already have % width michael@0: michael@0: int32_t scol, scol_end; michael@0: nsTableCellMap *cellMap = mTableFrame->GetCellMap(); michael@0: for (scol = aFirstCol, scol_end = aFirstCol + aColCount; michael@0: scol < scol_end; ++scol) { michael@0: nsTableColFrame *scolFrame = mTableFrame->GetColFrame(scol); michael@0: if (!scolFrame) { michael@0: NS_ERROR("column frames out of sync with cell map"); michael@0: continue; michael@0: } michael@0: float scolPct = scolFrame->GetPrefPercent(); michael@0: if (scolPct == 0.0f) { michael@0: nonPctTotalPrefWidth += scolFrame->GetPrefCoord(); michael@0: if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) { michael@0: ++nonPctColCount; michael@0: } michael@0: } else { michael@0: aSpanPrefPct -= scolPct; michael@0: } michael@0: } michael@0: michael@0: if (aSpanPrefPct <= 0.0f || nonPctColCount == 0) { michael@0: // There's no %-width on the colspan left over to distribute, michael@0: // or there are no columns to which we could distribute %-width michael@0: return; michael@0: } michael@0: michael@0: // Second loop, to distribute what remains of aSpanPrefPct michael@0: // between the non-percent-width spanned columns michael@0: const bool spanHasNonPctPref = nonPctTotalPrefWidth > 0; // Loop invariant michael@0: for (scol = aFirstCol, scol_end = aFirstCol + aColCount; michael@0: scol < scol_end; ++scol) { michael@0: nsTableColFrame *scolFrame = mTableFrame->GetColFrame(scol); michael@0: if (!scolFrame) { michael@0: NS_ERROR("column frames out of sync with cell map"); michael@0: continue; michael@0: } michael@0: michael@0: if (scolFrame->GetPrefPercent() == 0.0f) { michael@0: NS_ASSERTION((!spanHasNonPctPref || michael@0: nonPctTotalPrefWidth != 0) && michael@0: nonPctColCount != 0, michael@0: "should not be zero if we haven't allocated " michael@0: "all pref percent"); michael@0: michael@0: float allocatedPct; // % width to be given to this column michael@0: if (spanHasNonPctPref) { michael@0: // Group so we're multiplying by 1.0f when we need michael@0: // to use up aSpanPrefPct. michael@0: allocatedPct = aSpanPrefPct * michael@0: (float(scolFrame->GetPrefCoord()) / michael@0: float(nonPctTotalPrefWidth)); michael@0: } else if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) { michael@0: // distribute equally when all pref widths are 0 michael@0: allocatedPct = aSpanPrefPct / float(nonPctColCount); michael@0: } else { michael@0: allocatedPct = 0.0f; michael@0: } michael@0: // Allocate the percent michael@0: scolFrame->AddSpanPrefPercent(allocatedPct); michael@0: michael@0: // To avoid accumulating rounding error from division, michael@0: // subtract this column's values from the totals. michael@0: aSpanPrefPct -= allocatedPct; michael@0: nonPctTotalPrefWidth -= scolFrame->GetPrefCoord(); michael@0: if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) { michael@0: --nonPctColCount; michael@0: } michael@0: michael@0: if (!aSpanPrefPct) { michael@0: // No more span-percent-width to distribute --> we're done. michael@0: NS_ASSERTION(spanHasNonPctPref ? michael@0: nonPctTotalPrefWidth == 0 : michael@0: nonPctColCount == 0, michael@0: "No more pct width to distribute, but there are " michael@0: "still cols that need some."); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: BasicTableLayoutStrategy::DistributeWidthToColumns(nscoord aWidth, michael@0: int32_t aFirstCol, michael@0: int32_t aColCount, michael@0: BtlsWidthType aWidthType, michael@0: bool aSpanHasSpecifiedWidth) michael@0: { michael@0: NS_ASSERTION(aWidthType != BTLS_FINAL_WIDTH || michael@0: (aFirstCol == 0 && michael@0: aColCount == mTableFrame->GetCellMap()->GetColCount()), michael@0: "Computing final column widths, but didn't get full column range"); michael@0: michael@0: // border-spacing isn't part of the basis for percentages. michael@0: nscoord spacing = mTableFrame->GetCellSpacingX(); michael@0: nscoord subtract = 0; michael@0: // aWidth initially includes border-spacing for the boundaries in between michael@0: // each of the columns. We start at aFirstCol + 1 because the first michael@0: // in-between boundary would be at the left edge of column aFirstCol + 1 michael@0: for (int32_t col = aFirstCol + 1; col < aFirstCol + aColCount; ++col) { michael@0: if (mTableFrame->ColumnHasCellSpacingBefore(col)) { michael@0: subtract += spacing; michael@0: } michael@0: } michael@0: if (aWidthType == BTLS_FINAL_WIDTH) { michael@0: // If we're computing final col-width, then aWidth initially includes michael@0: // border spacing on the table's far left + far right edge, too. Need michael@0: // to subtract those out, too. michael@0: subtract += spacing * 2; michael@0: } michael@0: aWidth = NSCoordSaturatingSubtract(aWidth, subtract, nscoord_MAX); michael@0: michael@0: /* michael@0: * The goal of this function is to distribute |aWidth| between the michael@0: * columns by making an appropriate AddSpanCoords or SetFinalWidth michael@0: * call for each column. (We call AddSpanCoords if we're michael@0: * distributing a column-spanning cell's minimum or preferred width michael@0: * to its spanned columns. We call SetFinalWidth if we're michael@0: * distributing a table's final width to its columns.) michael@0: * michael@0: * The idea is to either assign one of the following sets of widths michael@0: * or a weighted average of two adjacent sets of widths. It is not michael@0: * possible to assign values smaller than the smallest set of michael@0: * widths. However, see below for handling the case of assigning michael@0: * values larger than the largest set of widths. From smallest to michael@0: * largest, these are: michael@0: * michael@0: * 1. [guess_min] Assign all columns their min width. michael@0: * michael@0: * 2. [guess_min_pct] Assign all columns with percentage widths michael@0: * their percentage width, and all other columns their min width. michael@0: * michael@0: * 3. [guess_min_spec] Assign all columns with percentage widths michael@0: * their percentage width, all columns with specified coordinate michael@0: * widths their pref width (since it doesn't matter whether it's the michael@0: * largest contributor to the pref width that was the specified michael@0: * contributor), and all other columns their min width. michael@0: * michael@0: * 4. [guess_pref] Assign all columns with percentage widths their michael@0: * specified width, and all other columns their pref width. michael@0: * michael@0: * If |aWidth| is *larger* than what we would assign in (4), then we michael@0: * expand the columns: michael@0: * michael@0: * a. if any columns without a specified coordinate width or michael@0: * percent width have nonzero pref width, in proportion to pref michael@0: * width [total_flex_pref] michael@0: * michael@0: * b. otherwise, if any columns without a specified coordinate michael@0: * width or percent width, but with cells originating in them, michael@0: * have zero pref width, equally between these michael@0: * [numNonSpecZeroWidthCols] michael@0: * michael@0: * c. otherwise, if any columns without percent width have nonzero michael@0: * pref width, in proportion to pref width [total_fixed_pref] michael@0: * michael@0: * d. otherwise, if any columns have nonzero percentage widths, in michael@0: * proportion to the percentage widths [total_pct] michael@0: * michael@0: * e. otherwise, equally. michael@0: */ michael@0: michael@0: // Loop #1 over the columns, to figure out the four values above so michael@0: // we know which case we're dealing with. michael@0: michael@0: nscoord guess_min = 0, michael@0: guess_min_pct = 0, michael@0: guess_min_spec = 0, michael@0: guess_pref = 0, michael@0: total_flex_pref = 0, michael@0: total_fixed_pref = 0; michael@0: float total_pct = 0.0f; // 0.0f to 1.0f michael@0: int32_t numInfiniteWidthCols = 0; michael@0: int32_t numNonSpecZeroWidthCols = 0; michael@0: michael@0: int32_t col; michael@0: nsTableCellMap *cellMap = mTableFrame->GetCellMap(); michael@0: for (col = aFirstCol; col < aFirstCol + aColCount; ++col) { michael@0: nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); michael@0: if (!colFrame) { michael@0: NS_ERROR("column frames out of sync with cell map"); michael@0: continue; michael@0: } michael@0: nscoord min_width = colFrame->GetMinCoord(); michael@0: guess_min += min_width; michael@0: if (colFrame->GetPrefPercent() != 0.0f) { michael@0: float pct = colFrame->GetPrefPercent(); michael@0: total_pct += pct; michael@0: nscoord val = nscoord(float(aWidth) * pct); michael@0: if (val < min_width) michael@0: val = min_width; michael@0: guess_min_pct += val; michael@0: guess_pref = NSCoordSaturatingAdd(guess_pref, val); michael@0: } else { michael@0: nscoord pref_width = colFrame->GetPrefCoord(); michael@0: if (pref_width == nscoord_MAX) { michael@0: ++numInfiniteWidthCols; michael@0: } michael@0: guess_pref = NSCoordSaturatingAdd(guess_pref, pref_width); michael@0: guess_min_pct += min_width; michael@0: if (colFrame->GetHasSpecifiedCoord()) { michael@0: // we'll add on the rest of guess_min_spec outside the michael@0: // loop michael@0: nscoord delta = NSCoordSaturatingSubtract(pref_width, michael@0: min_width, 0); michael@0: guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, delta); michael@0: total_fixed_pref = NSCoordSaturatingAdd(total_fixed_pref, michael@0: pref_width); michael@0: } else if (pref_width == 0) { michael@0: if (cellMap->GetNumCellsOriginatingInCol(col) > 0) { michael@0: ++numNonSpecZeroWidthCols; michael@0: } michael@0: } else { michael@0: total_flex_pref = NSCoordSaturatingAdd(total_flex_pref, michael@0: pref_width); michael@0: } michael@0: } michael@0: } michael@0: guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, guess_min_pct); michael@0: michael@0: // Determine what we're flexing: michael@0: enum Loop2Type { michael@0: FLEX_PCT_SMALL, // between (1) and (2) above michael@0: FLEX_FIXED_SMALL, // between (2) and (3) above michael@0: FLEX_FLEX_SMALL, // between (3) and (4) above michael@0: FLEX_FLEX_LARGE, // greater than (4) above, case (a) michael@0: FLEX_FLEX_LARGE_ZERO, // greater than (4) above, case (b) michael@0: FLEX_FIXED_LARGE, // greater than (4) above, case (c) michael@0: FLEX_PCT_LARGE, // greater than (4) above, case (d) michael@0: FLEX_ALL_LARGE // greater than (4) above, case (e) michael@0: }; michael@0: michael@0: Loop2Type l2t; michael@0: // These are constants (over columns) for each case's math. We use michael@0: // a pair of nscoords rather than a float so that we can subtract michael@0: // each column's allocation so we avoid accumulating rounding error. michael@0: nscoord space; // the amount of extra width to allocate michael@0: union { michael@0: nscoord c; michael@0: float f; michael@0: } basis; // the sum of the statistic over columns to divide it michael@0: if (aWidth < guess_pref) { michael@0: if (aWidthType != BTLS_FINAL_WIDTH && aWidth <= guess_min) { michael@0: // Return early -- we don't have any extra space to distribute. michael@0: return; michael@0: } michael@0: NS_ASSERTION(!(aWidthType == BTLS_FINAL_WIDTH && aWidth < guess_min), michael@0: "Table width is less than the " michael@0: "sum of its columns' min widths"); michael@0: if (aWidth < guess_min_pct) { michael@0: l2t = FLEX_PCT_SMALL; michael@0: space = aWidth - guess_min; michael@0: basis.c = guess_min_pct - guess_min; michael@0: } else if (aWidth < guess_min_spec) { michael@0: l2t = FLEX_FIXED_SMALL; michael@0: space = aWidth - guess_min_pct; michael@0: basis.c = NSCoordSaturatingSubtract(guess_min_spec, guess_min_pct, michael@0: nscoord_MAX); michael@0: } else { michael@0: l2t = FLEX_FLEX_SMALL; michael@0: space = aWidth - guess_min_spec; michael@0: basis.c = NSCoordSaturatingSubtract(guess_pref, guess_min_spec, michael@0: nscoord_MAX); michael@0: } michael@0: } else { michael@0: space = NSCoordSaturatingSubtract(aWidth, guess_pref, nscoord_MAX); michael@0: if (total_flex_pref > 0) { michael@0: l2t = FLEX_FLEX_LARGE; michael@0: basis.c = total_flex_pref; michael@0: } else if (numNonSpecZeroWidthCols > 0) { michael@0: l2t = FLEX_FLEX_LARGE_ZERO; michael@0: basis.c = numNonSpecZeroWidthCols; michael@0: } else if (total_fixed_pref > 0) { michael@0: l2t = FLEX_FIXED_LARGE; michael@0: basis.c = total_fixed_pref; michael@0: } else if (total_pct > 0.0f) { michael@0: l2t = FLEX_PCT_LARGE; michael@0: basis.f = total_pct; michael@0: } else { michael@0: l2t = FLEX_ALL_LARGE; michael@0: basis.c = aColCount; michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG_dbaron_off michael@0: printf("ComputeColumnWidths: %d columns in width %d,\n" michael@0: " guesses=[%d,%d,%d,%d], totals=[%d,%d,%f],\n" michael@0: " l2t=%d, space=%d, basis.c=%d\n", michael@0: aColCount, aWidth, michael@0: guess_min, guess_min_pct, guess_min_spec, guess_pref, michael@0: total_flex_pref, total_fixed_pref, total_pct, michael@0: l2t, space, basis.c); michael@0: #endif michael@0: michael@0: for (col = aFirstCol; col < aFirstCol + aColCount; ++col) { michael@0: nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); michael@0: if (!colFrame) { michael@0: NS_ERROR("column frames out of sync with cell map"); michael@0: continue; michael@0: } michael@0: nscoord col_width; michael@0: michael@0: float pct = colFrame->GetPrefPercent(); michael@0: if (pct != 0.0f) { michael@0: col_width = nscoord(float(aWidth) * pct); michael@0: nscoord col_min = colFrame->GetMinCoord(); michael@0: if (col_width < col_min) michael@0: col_width = col_min; michael@0: } else { michael@0: col_width = colFrame->GetPrefCoord(); michael@0: } michael@0: michael@0: nscoord col_width_before_adjust = col_width; michael@0: michael@0: switch (l2t) { michael@0: case FLEX_PCT_SMALL: michael@0: col_width = col_width_before_adjust = colFrame->GetMinCoord(); michael@0: if (pct != 0.0f) { michael@0: nscoord pct_minus_min = michael@0: nscoord(float(aWidth) * pct) - col_width; michael@0: if (pct_minus_min > 0) { michael@0: float c = float(space) / float(basis.c); michael@0: basis.c -= pct_minus_min; michael@0: col_width += NSToCoordRound(float(pct_minus_min) * c); michael@0: } michael@0: } michael@0: break; michael@0: case FLEX_FIXED_SMALL: michael@0: if (pct == 0.0f) { michael@0: NS_ASSERTION(col_width == colFrame->GetPrefCoord(), michael@0: "wrong width assigned"); michael@0: if (colFrame->GetHasSpecifiedCoord()) { michael@0: nscoord col_min = colFrame->GetMinCoord(); michael@0: nscoord pref_minus_min = col_width - col_min; michael@0: col_width = col_width_before_adjust = col_min; michael@0: if (pref_minus_min != 0) { michael@0: float c = float(space) / float(basis.c); michael@0: basis.c -= pref_minus_min; michael@0: col_width += NSToCoordRound( michael@0: float(pref_minus_min) * c); michael@0: } michael@0: } else michael@0: col_width = col_width_before_adjust = michael@0: colFrame->GetMinCoord(); michael@0: } michael@0: break; michael@0: case FLEX_FLEX_SMALL: michael@0: if (pct == 0.0f && michael@0: !colFrame->GetHasSpecifiedCoord()) { michael@0: NS_ASSERTION(col_width == colFrame->GetPrefCoord(), michael@0: "wrong width assigned"); michael@0: nscoord col_min = colFrame->GetMinCoord(); michael@0: nscoord pref_minus_min = michael@0: NSCoordSaturatingSubtract(col_width, col_min, 0); michael@0: col_width = col_width_before_adjust = col_min; michael@0: if (pref_minus_min != 0) { michael@0: float c = float(space) / float(basis.c); michael@0: // If we have infinite-width cols, then the standard michael@0: // adjustment to col_width using 'c' won't work, michael@0: // because basis.c and pref_minus_min are both michael@0: // nscoord_MAX and will cancel each other out in the michael@0: // col_width adjustment (making us assign all the michael@0: // space to the first inf-width col). To correct for michael@0: // this, we'll also divide by numInfiniteWidthCols to michael@0: // spread the space equally among the inf-width cols. michael@0: if (numInfiniteWidthCols) { michael@0: if (colFrame->GetPrefCoord() == nscoord_MAX) { michael@0: c = c / float(numInfiniteWidthCols); michael@0: --numInfiniteWidthCols; michael@0: } else { michael@0: c = 0.0f; michael@0: } michael@0: } michael@0: basis.c = NSCoordSaturatingSubtract(basis.c, michael@0: pref_minus_min, michael@0: nscoord_MAX); michael@0: col_width += NSToCoordRound( michael@0: float(pref_minus_min) * c); michael@0: } michael@0: } michael@0: break; michael@0: case FLEX_FLEX_LARGE: michael@0: if (pct == 0.0f && michael@0: !colFrame->GetHasSpecifiedCoord()) { michael@0: NS_ASSERTION(col_width == colFrame->GetPrefCoord(), michael@0: "wrong width assigned"); michael@0: if (col_width != 0) { michael@0: if (space == nscoord_MAX) { michael@0: basis.c -= col_width; michael@0: col_width = nscoord_MAX; michael@0: } else { michael@0: float c = float(space) / float(basis.c); michael@0: basis.c -= col_width; michael@0: col_width += NSToCoordRound(float(col_width) * c); michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: case FLEX_FLEX_LARGE_ZERO: michael@0: if (pct == 0.0f && michael@0: !colFrame->GetHasSpecifiedCoord() && michael@0: cellMap->GetNumCellsOriginatingInCol(col) > 0) { michael@0: michael@0: NS_ASSERTION(col_width == 0 && michael@0: colFrame->GetPrefCoord() == 0, michael@0: "Since we're in FLEX_FLEX_LARGE_ZERO case, " michael@0: "all auto-width cols should have zero pref " michael@0: "width."); michael@0: float c = float(space) / float(basis.c); michael@0: col_width += NSToCoordRound(c); michael@0: --basis.c; michael@0: } michael@0: break; michael@0: case FLEX_FIXED_LARGE: michael@0: if (pct == 0.0f) { michael@0: NS_ASSERTION(col_width == colFrame->GetPrefCoord(), michael@0: "wrong width assigned"); michael@0: NS_ASSERTION(colFrame->GetHasSpecifiedCoord() || michael@0: colFrame->GetPrefCoord() == 0, michael@0: "wrong case"); michael@0: if (col_width != 0) { michael@0: float c = float(space) / float(basis.c); michael@0: basis.c -= col_width; michael@0: col_width += NSToCoordRound(float(col_width) * c); michael@0: } michael@0: } michael@0: break; michael@0: case FLEX_PCT_LARGE: michael@0: NS_ASSERTION(pct != 0.0f || colFrame->GetPrefCoord() == 0, michael@0: "wrong case"); michael@0: if (pct != 0.0f) { michael@0: float c = float(space) / basis.f; michael@0: col_width += NSToCoordRound(pct * c); michael@0: basis.f -= pct; michael@0: } michael@0: break; michael@0: case FLEX_ALL_LARGE: michael@0: { michael@0: float c = float(space) / float(basis.c); michael@0: col_width += NSToCoordRound(c); michael@0: --basis.c; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: // Only subtract from space if it's a real number. michael@0: if (space != nscoord_MAX) { michael@0: NS_ASSERTION(col_width != nscoord_MAX, michael@0: "How is col_width nscoord_MAX if space isn't?"); michael@0: NS_ASSERTION(col_width_before_adjust != nscoord_MAX, michael@0: "How is col_width_before_adjust nscoord_MAX if space isn't?"); michael@0: space -= col_width - col_width_before_adjust; michael@0: } michael@0: michael@0: NS_ASSERTION(col_width >= colFrame->GetMinCoord(), michael@0: "assigned width smaller than min"); michael@0: michael@0: // Apply the new width michael@0: switch (aWidthType) { michael@0: case BTLS_MIN_WIDTH: michael@0: { michael@0: // Note: AddSpanCoords requires both a min and pref width. michael@0: // For the pref width, we'll just pass in our computed michael@0: // min width, because the real pref width will be at least michael@0: // as big michael@0: colFrame->AddSpanCoords(col_width, col_width, michael@0: aSpanHasSpecifiedWidth); michael@0: } michael@0: break; michael@0: case BTLS_PREF_WIDTH: michael@0: { michael@0: // Note: AddSpanCoords requires both a min and pref width. michael@0: // For the min width, we'll just pass in 0, because michael@0: // the real min width will be at least 0 michael@0: colFrame->AddSpanCoords(0, col_width, michael@0: aSpanHasSpecifiedWidth); michael@0: } michael@0: break; michael@0: case BTLS_FINAL_WIDTH: michael@0: { michael@0: nscoord old_final = colFrame->GetFinalWidth(); michael@0: colFrame->SetFinalWidth(col_width); michael@0: michael@0: if (old_final != col_width) michael@0: mTableFrame->DidResizeColumns(); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: NS_ASSERTION((space == 0 || space == nscoord_MAX) && michael@0: ((l2t == FLEX_PCT_LARGE) michael@0: ? (-0.001f < basis.f && basis.f < 0.001f) michael@0: : (basis.c == 0 || basis.c == nscoord_MAX)), michael@0: "didn't subtract all that we added"); michael@0: }