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: * Algorithms that determine column and table widths used for CSS2's michael@0: * 'table-layout: fixed'. michael@0: */ michael@0: michael@0: #include "FixedTableLayoutStrategy.h" michael@0: #include "nsTableFrame.h" michael@0: #include "nsTableColFrame.h" michael@0: #include "nsTableCellFrame.h" michael@0: #include michael@0: michael@0: FixedTableLayoutStrategy::FixedTableLayoutStrategy(nsTableFrame *aTableFrame) michael@0: : nsITableLayoutStrategy(nsITableLayoutStrategy::Fixed) michael@0: , mTableFrame(aTableFrame) michael@0: { michael@0: MarkIntrinsicWidthsDirty(); michael@0: } michael@0: michael@0: /* virtual */ michael@0: FixedTableLayoutStrategy::~FixedTableLayoutStrategy() michael@0: { michael@0: } michael@0: michael@0: /* virtual */ nscoord michael@0: FixedTableLayoutStrategy::GetMinWidth(nsRenderingContext* aRenderingContext) michael@0: { michael@0: DISPLAY_MIN_WIDTH(mTableFrame, mMinWidth); michael@0: if (mMinWidth != NS_INTRINSIC_WIDTH_UNKNOWN) michael@0: return mMinWidth; michael@0: michael@0: // It's theoretically possible to do something much better here that michael@0: // depends only on the columns and the first row (where we look at michael@0: // intrinsic widths inside the first row and then reverse the michael@0: // algorithm to find the narrowest width that would hold all of michael@0: // those intrinsic widths), but it wouldn't be compatible with other michael@0: // browsers, or with the use of GetMinWidth by michael@0: // nsTableFrame::ComputeSize to determine the width of a fixed michael@0: // layout table, since CSS2.1 says: michael@0: // The width of the table is then the greater of the value of the michael@0: // 'width' property for the table element and the sum of the michael@0: // column widths (plus cell spacing or borders). michael@0: michael@0: // XXX Should we really ignore 'min-width' and 'max-width'? michael@0: // XXX Should we really ignore widths on column groups? michael@0: michael@0: nsTableCellMap *cellMap = mTableFrame->GetCellMap(); michael@0: int32_t colCount = cellMap->GetColCount(); michael@0: nscoord spacing = mTableFrame->GetCellSpacingX(); michael@0: michael@0: nscoord result = 0; michael@0: michael@0: if (colCount > 0) { michael@0: result += spacing * (colCount + 1); michael@0: } 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: const nsStyleCoord *styleWidth = michael@0: &colFrame->StylePosition()->mWidth; michael@0: if (styleWidth->ConvertsToLength()) { michael@0: result += nsLayoutUtils::ComputeWidthValue(aRenderingContext, michael@0: colFrame, 0, 0, 0, *styleWidth); michael@0: } else if (styleWidth->GetUnit() == eStyleUnit_Percent) { michael@0: // do nothing michael@0: } else { michael@0: NS_ASSERTION(styleWidth->GetUnit() == eStyleUnit_Auto || michael@0: styleWidth->GetUnit() == eStyleUnit_Enumerated || michael@0: (styleWidth->IsCalcUnit() && styleWidth->CalcHasPercent()), michael@0: "bad width"); michael@0: michael@0: // The 'table-layout: fixed' algorithm considers only cells michael@0: // in the first row. michael@0: bool originates; michael@0: int32_t colSpan; michael@0: nsTableCellFrame *cellFrame = michael@0: cellMap->GetCellInfoAt(0, col, &originates, &colSpan); michael@0: if (cellFrame) { michael@0: styleWidth = &cellFrame->StylePosition()->mWidth; michael@0: if (styleWidth->ConvertsToLength() || michael@0: (styleWidth->GetUnit() == eStyleUnit_Enumerated && michael@0: (styleWidth->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT || michael@0: styleWidth->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT))) { michael@0: nscoord cellWidth = nsLayoutUtils::IntrinsicForContainer( michael@0: aRenderingContext, cellFrame, nsLayoutUtils::MIN_WIDTH); michael@0: if (colSpan > 1) { michael@0: // If a column-spanning cell is in the first michael@0: // row, split up the space evenly. (XXX This michael@0: // isn't quite right if some of the columns it's michael@0: // in have specified widths. Should we care?) michael@0: cellWidth = ((cellWidth + spacing) / colSpan) - spacing; michael@0: } michael@0: result += cellWidth; michael@0: } else if (styleWidth->GetUnit() == eStyleUnit_Percent) { michael@0: if (colSpan > 1) { michael@0: // XXX Can this force columns to negative michael@0: // widths? michael@0: result -= spacing * (colSpan - 1); michael@0: } michael@0: } michael@0: // else, for 'auto', '-moz-available', '-moz-fit-content', michael@0: // and 'calc()' with percentages, do nothing michael@0: } michael@0: } michael@0: } michael@0: michael@0: return (mMinWidth = result); michael@0: } michael@0: michael@0: /* virtual */ nscoord michael@0: FixedTableLayoutStrategy::GetPrefWidth(nsRenderingContext* aRenderingContext, michael@0: bool aComputingSize) michael@0: { michael@0: // It's theoretically possible to do something much better here that michael@0: // depends only on the columns and the first row (where we look at michael@0: // intrinsic widths inside the first row and then reverse the michael@0: // algorithm to find the narrowest width that would hold all of michael@0: // those intrinsic widths), but it wouldn't be compatible with other michael@0: // browsers. michael@0: nscoord result = nscoord_MAX; michael@0: DISPLAY_PREF_WIDTH(mTableFrame, result); michael@0: return result; michael@0: } michael@0: michael@0: /* virtual */ void michael@0: FixedTableLayoutStrategy::MarkIntrinsicWidthsDirty() michael@0: { michael@0: mMinWidth = NS_INTRINSIC_WIDTH_UNKNOWN; michael@0: mLastCalcWidth = nscoord_MIN; michael@0: } michael@0: michael@0: static inline nscoord michael@0: AllocateUnassigned(nscoord aUnassignedSpace, float aShare) michael@0: { michael@0: if (aShare == 1.0f) { michael@0: // This happens when the numbers we're dividing to get aShare michael@0: // are equal. We want to return unassignedSpace exactly, even michael@0: // if it can't be precisely round-tripped through float. michael@0: return aUnassignedSpace; michael@0: } michael@0: return NSToCoordRound(float(aUnassignedSpace) * aShare); michael@0: } michael@0: michael@0: /* virtual */ void michael@0: FixedTableLayoutStrategy::ComputeColumnWidths(const nsHTMLReflowState& aReflowState) michael@0: { michael@0: nscoord tableWidth = aReflowState.ComputedWidth(); michael@0: michael@0: if (mLastCalcWidth == tableWidth) michael@0: return; michael@0: mLastCalcWidth = tableWidth; michael@0: michael@0: nsTableCellMap *cellMap = mTableFrame->GetCellMap(); michael@0: int32_t colCount = cellMap->GetColCount(); michael@0: nscoord spacing = mTableFrame->GetCellSpacingX(); michael@0: michael@0: if (colCount == 0) { michael@0: // No Columns - nothing to compute michael@0: return; michael@0: } michael@0: michael@0: // border-spacing isn't part of the basis for percentages. michael@0: tableWidth -= spacing * (colCount + 1); michael@0: michael@0: // store the old column widths. We might call multiple times SetFinalWidth michael@0: // on the columns, due to this we can't compare at the last call that the michael@0: // width has changed with the respect to the last call to michael@0: // ComputeColumnWidths. In order to overcome this we store the old values michael@0: // in this array. A single call to SetFinalWidth would make it possible to michael@0: // call GetFinalWidth before and to compare when setting the final width. michael@0: nsTArray oldColWidths; michael@0: michael@0: // XXX This ignores the 'min-width' and 'max-width' properties michael@0: // throughout. Then again, that's what the CSS spec says to do. michael@0: michael@0: // XXX Should we really ignore widths on column groups? michael@0: michael@0: uint32_t unassignedCount = 0; michael@0: nscoord unassignedSpace = tableWidth; michael@0: const nscoord unassignedMarker = nscoord_MIN; michael@0: michael@0: // We use the PrefPercent on the columns to store the percentages michael@0: // used to compute column widths in case we need to shrink or expand michael@0: // the columns. michael@0: float pctTotal = 0.0f; michael@0: michael@0: // Accumulate the total specified (non-percent) on the columns for michael@0: // distributing excess width to the columns. michael@0: nscoord specTotal = 0; 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: oldColWidths.AppendElement(0); michael@0: NS_ERROR("column frames out of sync with cell map"); michael@0: continue; michael@0: } michael@0: oldColWidths.AppendElement(colFrame->GetFinalWidth()); michael@0: colFrame->ResetPrefPercent(); michael@0: const nsStyleCoord *styleWidth = michael@0: &colFrame->StylePosition()->mWidth; michael@0: nscoord colWidth; michael@0: if (styleWidth->ConvertsToLength()) { michael@0: colWidth = nsLayoutUtils::ComputeWidthValue( michael@0: aReflowState.rendContext, michael@0: colFrame, 0, 0, 0, *styleWidth); michael@0: specTotal += colWidth; michael@0: } else if (styleWidth->GetUnit() == eStyleUnit_Percent) { michael@0: float pct = styleWidth->GetPercentValue(); michael@0: colWidth = NSToCoordFloor(pct * float(tableWidth)); michael@0: colFrame->AddPrefPercent(pct); michael@0: pctTotal += pct; michael@0: } else { michael@0: NS_ASSERTION(styleWidth->GetUnit() == eStyleUnit_Auto || michael@0: styleWidth->GetUnit() == eStyleUnit_Enumerated || michael@0: (styleWidth->IsCalcUnit() && styleWidth->CalcHasPercent()), michael@0: "bad width"); michael@0: michael@0: // The 'table-layout: fixed' algorithm considers only cells michael@0: // in the first row. michael@0: bool originates; michael@0: int32_t colSpan; michael@0: nsTableCellFrame *cellFrame = michael@0: cellMap->GetCellInfoAt(0, col, &originates, &colSpan); michael@0: if (cellFrame) { michael@0: styleWidth = &cellFrame->StylePosition()->mWidth; michael@0: if (styleWidth->ConvertsToLength() || michael@0: (styleWidth->GetUnit() == eStyleUnit_Enumerated && michael@0: (styleWidth->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT || michael@0: styleWidth->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT))) { michael@0: // XXX This should use real percentage padding michael@0: // Note that the difference between MIN_WIDTH and michael@0: // PREF_WIDTH shouldn't matter for any of these michael@0: // values of styleWidth; use MIN_WIDTH for symmetry michael@0: // with GetMinWidth above, just in case there is a michael@0: // difference. michael@0: colWidth = nsLayoutUtils::IntrinsicForContainer( michael@0: aReflowState.rendContext, michael@0: cellFrame, nsLayoutUtils::MIN_WIDTH); michael@0: } else if (styleWidth->GetUnit() == eStyleUnit_Percent) { michael@0: // XXX This should use real percentage padding michael@0: nsIFrame::IntrinsicWidthOffsetData offsets = michael@0: cellFrame->IntrinsicWidthOffsets(aReflowState.rendContext); michael@0: float pct = styleWidth->GetPercentValue(); michael@0: colWidth = NSToCoordFloor(pct * float(tableWidth)); michael@0: michael@0: nscoord boxSizingAdjust = 0; michael@0: switch (cellFrame->StylePosition()->mBoxSizing) { michael@0: case NS_STYLE_BOX_SIZING_CONTENT: michael@0: boxSizingAdjust += offsets.hPadding; michael@0: // Fall through michael@0: case NS_STYLE_BOX_SIZING_PADDING: michael@0: boxSizingAdjust += offsets.hBorder; michael@0: // Fall through michael@0: case NS_STYLE_BOX_SIZING_BORDER: michael@0: // Don't add anything michael@0: break; michael@0: } michael@0: colWidth += boxSizingAdjust; michael@0: michael@0: pct /= float(colSpan); michael@0: colFrame->AddPrefPercent(pct); michael@0: pctTotal += pct; michael@0: } else { michael@0: // 'auto', '-moz-available', '-moz-fit-content', and michael@0: // 'calc()' with percentages michael@0: colWidth = unassignedMarker; michael@0: } michael@0: if (colWidth != unassignedMarker) { michael@0: if (colSpan > 1) { michael@0: // If a column-spanning cell is in the first michael@0: // row, split up the space evenly. (XXX This michael@0: // isn't quite right if some of the columns it's michael@0: // in have specified widths. Should we care?) michael@0: colWidth = ((colWidth + spacing) / colSpan) - spacing; michael@0: if (colWidth < 0) michael@0: colWidth = 0; michael@0: } michael@0: if (styleWidth->GetUnit() != eStyleUnit_Percent) { michael@0: specTotal += colWidth; michael@0: } michael@0: } michael@0: } else { michael@0: colWidth = unassignedMarker; michael@0: } michael@0: } michael@0: michael@0: colFrame->SetFinalWidth(colWidth); michael@0: michael@0: if (colWidth == unassignedMarker) { michael@0: ++unassignedCount; michael@0: } else { michael@0: unassignedSpace -= colWidth; michael@0: } michael@0: } michael@0: michael@0: if (unassignedSpace < 0) { michael@0: if (pctTotal > 0) { michael@0: // If the columns took up too much space, reduce those that michael@0: // had percentage widths. The spec doesn't say to do this, michael@0: // but we've always done it in the past, and so does WinIE6. michael@0: nscoord pctUsed = NSToCoordFloor(pctTotal * float(tableWidth)); michael@0: nscoord reduce = std::min(pctUsed, -unassignedSpace); michael@0: float reduceRatio = float(reduce) / pctTotal; 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: nscoord colWidth = colFrame->GetFinalWidth(); michael@0: colWidth -= NSToCoordFloor(colFrame->GetPrefPercent() * michael@0: reduceRatio); michael@0: if (colWidth < 0) michael@0: colWidth = 0; michael@0: colFrame->SetFinalWidth(colWidth); michael@0: } michael@0: } michael@0: unassignedSpace = 0; michael@0: } michael@0: michael@0: if (unassignedCount > 0) { michael@0: // The spec says to distribute the remaining space evenly among michael@0: // the columns. michael@0: nscoord toAssign = unassignedSpace / unassignedCount; 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 (colFrame->GetFinalWidth() == unassignedMarker) michael@0: colFrame->SetFinalWidth(toAssign); michael@0: } michael@0: } else if (unassignedSpace > 0) { michael@0: // The spec doesn't say how to distribute the unassigned space. michael@0: if (specTotal > 0) { michael@0: // Distribute proportionally to non-percentage columns. michael@0: nscoord specUndist = specTotal; 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 (colFrame->GetPrefPercent() == 0.0f) { michael@0: NS_ASSERTION(colFrame->GetFinalWidth() <= specUndist, michael@0: "widths don't add up"); michael@0: nscoord toAdd = AllocateUnassigned(unassignedSpace, michael@0: float(colFrame->GetFinalWidth()) / float(specUndist)); michael@0: specUndist -= colFrame->GetFinalWidth(); michael@0: colFrame->SetFinalWidth(colFrame->GetFinalWidth() + toAdd); michael@0: unassignedSpace -= toAdd; michael@0: if (specUndist <= 0) { michael@0: NS_ASSERTION(specUndist == 0, michael@0: "math should be exact"); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: NS_ASSERTION(unassignedSpace == 0, "failed to redistribute"); michael@0: } else if (pctTotal > 0) { michael@0: // Distribute proportionally to percentage columns. michael@0: float pctUndist = pctTotal; 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 (pctUndist < colFrame->GetPrefPercent()) { michael@0: // This can happen with floating-point math. michael@0: NS_ASSERTION(colFrame->GetPrefPercent() - pctUndist michael@0: < 0.0001, michael@0: "widths don't add up"); michael@0: pctUndist = colFrame->GetPrefPercent(); michael@0: } michael@0: nscoord toAdd = AllocateUnassigned(unassignedSpace, michael@0: colFrame->GetPrefPercent() / pctUndist); michael@0: colFrame->SetFinalWidth(colFrame->GetFinalWidth() + toAdd); michael@0: unassignedSpace -= toAdd; michael@0: pctUndist -= colFrame->GetPrefPercent(); michael@0: if (pctUndist <= 0.0f) { michael@0: break; michael@0: } michael@0: } michael@0: NS_ASSERTION(unassignedSpace == 0, "failed to redistribute"); michael@0: } else { michael@0: // Distribute equally to the zero-width columns. michael@0: int32_t colsLeft = colCount; 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: NS_ASSERTION(colFrame->GetFinalWidth() == 0, "yikes"); michael@0: nscoord toAdd = AllocateUnassigned(unassignedSpace, michael@0: 1.0f / float(colsLeft)); michael@0: colFrame->SetFinalWidth(toAdd); michael@0: unassignedSpace -= toAdd; michael@0: --colsLeft; michael@0: } michael@0: NS_ASSERTION(unassignedSpace == 0, "failed to redistribute"); michael@0: } 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 (oldColWidths.ElementAt(col) != colFrame->GetFinalWidth()) { michael@0: mTableFrame->DidResizeColumns(); michael@0: break; michael@0: } michael@0: } michael@0: }