Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
michael@0 | 1 | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
michael@0 | 2 | // vim:cindent:ts=4:et:sw=4: |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | /* |
michael@0 | 8 | * Web-compatible algorithms that determine column and table widths, |
michael@0 | 9 | * used for CSS2's 'table-layout: auto'. |
michael@0 | 10 | */ |
michael@0 | 11 | |
michael@0 | 12 | #include "BasicTableLayoutStrategy.h" |
michael@0 | 13 | #include "nsTableFrame.h" |
michael@0 | 14 | #include "nsTableCellFrame.h" |
michael@0 | 15 | #include "nsLayoutUtils.h" |
michael@0 | 16 | #include "nsGkAtoms.h" |
michael@0 | 17 | #include "SpanningCellSorter.h" |
michael@0 | 18 | #include <algorithm> |
michael@0 | 19 | |
michael@0 | 20 | using namespace mozilla; |
michael@0 | 21 | using namespace mozilla::layout; |
michael@0 | 22 | |
michael@0 | 23 | namespace css = mozilla::css; |
michael@0 | 24 | |
michael@0 | 25 | #undef DEBUG_TABLE_STRATEGY |
michael@0 | 26 | |
michael@0 | 27 | BasicTableLayoutStrategy::BasicTableLayoutStrategy(nsTableFrame *aTableFrame) |
michael@0 | 28 | : nsITableLayoutStrategy(nsITableLayoutStrategy::Auto) |
michael@0 | 29 | , mTableFrame(aTableFrame) |
michael@0 | 30 | { |
michael@0 | 31 | MarkIntrinsicWidthsDirty(); |
michael@0 | 32 | } |
michael@0 | 33 | |
michael@0 | 34 | /* virtual */ |
michael@0 | 35 | BasicTableLayoutStrategy::~BasicTableLayoutStrategy() |
michael@0 | 36 | { |
michael@0 | 37 | } |
michael@0 | 38 | |
michael@0 | 39 | /* virtual */ nscoord |
michael@0 | 40 | BasicTableLayoutStrategy::GetMinWidth(nsRenderingContext* aRenderingContext) |
michael@0 | 41 | { |
michael@0 | 42 | DISPLAY_MIN_WIDTH(mTableFrame, mMinWidth); |
michael@0 | 43 | if (mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN) |
michael@0 | 44 | ComputeIntrinsicWidths(aRenderingContext); |
michael@0 | 45 | return mMinWidth; |
michael@0 | 46 | } |
michael@0 | 47 | |
michael@0 | 48 | /* virtual */ nscoord |
michael@0 | 49 | BasicTableLayoutStrategy::GetPrefWidth(nsRenderingContext* aRenderingContext, |
michael@0 | 50 | bool aComputingSize) |
michael@0 | 51 | { |
michael@0 | 52 | DISPLAY_PREF_WIDTH(mTableFrame, mPrefWidth); |
michael@0 | 53 | NS_ASSERTION((mPrefWidth == NS_INTRINSIC_WIDTH_UNKNOWN) == |
michael@0 | 54 | (mPrefWidthPctExpand == NS_INTRINSIC_WIDTH_UNKNOWN), |
michael@0 | 55 | "dirtyness out of sync"); |
michael@0 | 56 | if (mPrefWidth == NS_INTRINSIC_WIDTH_UNKNOWN) |
michael@0 | 57 | ComputeIntrinsicWidths(aRenderingContext); |
michael@0 | 58 | return aComputingSize ? mPrefWidthPctExpand : mPrefWidth; |
michael@0 | 59 | } |
michael@0 | 60 | |
michael@0 | 61 | struct CellWidthInfo { |
michael@0 | 62 | CellWidthInfo(nscoord aMinCoord, nscoord aPrefCoord, |
michael@0 | 63 | float aPrefPercent, bool aHasSpecifiedWidth) |
michael@0 | 64 | : hasSpecifiedWidth(aHasSpecifiedWidth) |
michael@0 | 65 | , minCoord(aMinCoord) |
michael@0 | 66 | , prefCoord(aPrefCoord) |
michael@0 | 67 | , prefPercent(aPrefPercent) |
michael@0 | 68 | { |
michael@0 | 69 | } |
michael@0 | 70 | |
michael@0 | 71 | bool hasSpecifiedWidth; |
michael@0 | 72 | nscoord minCoord; |
michael@0 | 73 | nscoord prefCoord; |
michael@0 | 74 | float prefPercent; |
michael@0 | 75 | }; |
michael@0 | 76 | |
michael@0 | 77 | // Used for both column and cell calculations. The parts needed only |
michael@0 | 78 | // for cells are skipped when aIsCell is false. |
michael@0 | 79 | static CellWidthInfo |
michael@0 | 80 | GetWidthInfo(nsRenderingContext *aRenderingContext, |
michael@0 | 81 | nsIFrame *aFrame, bool aIsCell) |
michael@0 | 82 | { |
michael@0 | 83 | nscoord minCoord, prefCoord; |
michael@0 | 84 | const nsStylePosition *stylePos = aFrame->StylePosition(); |
michael@0 | 85 | bool isQuirks = aFrame->PresContext()->CompatibilityMode() == |
michael@0 | 86 | eCompatibility_NavQuirks; |
michael@0 | 87 | nscoord boxSizingToBorderEdge = 0; |
michael@0 | 88 | if (aIsCell) { |
michael@0 | 89 | // If aFrame is a container for font size inflation, then shrink |
michael@0 | 90 | // wrapping inside of it should not apply font size inflation. |
michael@0 | 91 | AutoMaybeDisableFontInflation an(aFrame); |
michael@0 | 92 | |
michael@0 | 93 | minCoord = aFrame->GetMinWidth(aRenderingContext); |
michael@0 | 94 | prefCoord = aFrame->GetPrefWidth(aRenderingContext); |
michael@0 | 95 | // Until almost the end of this function, minCoord and prefCoord |
michael@0 | 96 | // represent the box-sizing based width values (which mean they |
michael@0 | 97 | // should include horizontal padding and border width when |
michael@0 | 98 | // box-sizing is set to border-box). |
michael@0 | 99 | // Note that this function returns border-box width, we add the |
michael@0 | 100 | // outer edges near the end of this function. |
michael@0 | 101 | |
michael@0 | 102 | // XXX Should we ignore percentage padding? |
michael@0 | 103 | nsIFrame::IntrinsicWidthOffsetData offsets = aFrame->IntrinsicWidthOffsets(aRenderingContext); |
michael@0 | 104 | |
michael@0 | 105 | // In quirks mode, table cell width should be content-box, |
michael@0 | 106 | // but height should be border box. |
michael@0 | 107 | // Because of this historic anomaly, we do not use quirk.css. |
michael@0 | 108 | // (We can't specify one value of box-sizing for width and another |
michael@0 | 109 | // for height). |
michael@0 | 110 | // For this reason, we also do not use box-sizing for just one of |
michael@0 | 111 | // them, as this may be confusing. |
michael@0 | 112 | if (isQuirks) { |
michael@0 | 113 | boxSizingToBorderEdge = offsets.hPadding + offsets.hBorder; |
michael@0 | 114 | } |
michael@0 | 115 | else { |
michael@0 | 116 | switch (stylePos->mBoxSizing) { |
michael@0 | 117 | case NS_STYLE_BOX_SIZING_CONTENT: |
michael@0 | 118 | boxSizingToBorderEdge = offsets.hPadding + offsets.hBorder; |
michael@0 | 119 | break; |
michael@0 | 120 | case NS_STYLE_BOX_SIZING_PADDING: |
michael@0 | 121 | minCoord += offsets.hPadding; |
michael@0 | 122 | prefCoord += offsets.hPadding; |
michael@0 | 123 | boxSizingToBorderEdge = offsets.hBorder; |
michael@0 | 124 | break; |
michael@0 | 125 | default: |
michael@0 | 126 | // NS_STYLE_BOX_SIZING_BORDER |
michael@0 | 127 | minCoord += offsets.hPadding + offsets.hBorder; |
michael@0 | 128 | prefCoord += offsets.hPadding + offsets.hBorder; |
michael@0 | 129 | break; |
michael@0 | 130 | } |
michael@0 | 131 | } |
michael@0 | 132 | } else { |
michael@0 | 133 | minCoord = 0; |
michael@0 | 134 | prefCoord = 0; |
michael@0 | 135 | } |
michael@0 | 136 | float prefPercent = 0.0f; |
michael@0 | 137 | bool hasSpecifiedWidth = false; |
michael@0 | 138 | |
michael@0 | 139 | const nsStyleCoord &width = stylePos->mWidth; |
michael@0 | 140 | nsStyleUnit unit = width.GetUnit(); |
michael@0 | 141 | // NOTE: We're ignoring calc() units with percentages here, for lack of a |
michael@0 | 142 | // sensible idea for what to do with them. This means calc() with |
michael@0 | 143 | // percentages is basically handled like 'auto' for table cells and |
michael@0 | 144 | // columns. |
michael@0 | 145 | if (width.ConvertsToLength()) { |
michael@0 | 146 | hasSpecifiedWidth = true; |
michael@0 | 147 | // Note: since ComputeWidthValue was designed to return content-box |
michael@0 | 148 | // width, it will (in some cases) subtract the box-sizing edges. |
michael@0 | 149 | // We prevent this unwanted behavior by calling it with |
michael@0 | 150 | // aContentEdgeToBoxSizing and aBoxSizingToMarginEdge set to 0. |
michael@0 | 151 | nscoord w = nsLayoutUtils::ComputeWidthValue(aRenderingContext, |
michael@0 | 152 | aFrame, 0, 0, 0, width); |
michael@0 | 153 | // Quirk: A cell with "nowrap" set and a coord value for the |
michael@0 | 154 | // width which is bigger than the intrinsic minimum width uses |
michael@0 | 155 | // that coord value as the minimum width. |
michael@0 | 156 | // This is kept up-to-date with dynamic changes to nowrap by code in |
michael@0 | 157 | // nsTableCellFrame::AttributeChanged |
michael@0 | 158 | if (aIsCell && w > minCoord && isQuirks && |
michael@0 | 159 | aFrame->GetContent()->HasAttr(kNameSpaceID_None, |
michael@0 | 160 | nsGkAtoms::nowrap)) { |
michael@0 | 161 | minCoord = w; |
michael@0 | 162 | } |
michael@0 | 163 | prefCoord = std::max(w, minCoord); |
michael@0 | 164 | } else if (unit == eStyleUnit_Percent) { |
michael@0 | 165 | prefPercent = width.GetPercentValue(); |
michael@0 | 166 | } else if (unit == eStyleUnit_Enumerated && aIsCell) { |
michael@0 | 167 | switch (width.GetIntValue()) { |
michael@0 | 168 | case NS_STYLE_WIDTH_MAX_CONTENT: |
michael@0 | 169 | // 'width' only affects pref width, not min |
michael@0 | 170 | // width, so don't change anything |
michael@0 | 171 | break; |
michael@0 | 172 | case NS_STYLE_WIDTH_MIN_CONTENT: |
michael@0 | 173 | prefCoord = minCoord; |
michael@0 | 174 | break; |
michael@0 | 175 | case NS_STYLE_WIDTH_FIT_CONTENT: |
michael@0 | 176 | case NS_STYLE_WIDTH_AVAILABLE: |
michael@0 | 177 | // act just like 'width: auto' |
michael@0 | 178 | break; |
michael@0 | 179 | default: |
michael@0 | 180 | NS_NOTREACHED("unexpected enumerated value"); |
michael@0 | 181 | } |
michael@0 | 182 | } |
michael@0 | 183 | |
michael@0 | 184 | nsStyleCoord maxWidth(stylePos->mMaxWidth); |
michael@0 | 185 | if (maxWidth.GetUnit() == eStyleUnit_Enumerated) { |
michael@0 | 186 | if (!aIsCell || maxWidth.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE) |
michael@0 | 187 | maxWidth.SetNoneValue(); |
michael@0 | 188 | else if (maxWidth.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT) |
michael@0 | 189 | // for 'max-width', '-moz-fit-content' is like |
michael@0 | 190 | // '-moz-max-content' |
michael@0 | 191 | maxWidth.SetIntValue(NS_STYLE_WIDTH_MAX_CONTENT, |
michael@0 | 192 | eStyleUnit_Enumerated); |
michael@0 | 193 | } |
michael@0 | 194 | unit = maxWidth.GetUnit(); |
michael@0 | 195 | // XXX To really implement 'max-width' well, we'd need to store |
michael@0 | 196 | // it separately on the columns. |
michael@0 | 197 | if (maxWidth.ConvertsToLength() || unit == eStyleUnit_Enumerated) { |
michael@0 | 198 | nscoord w = |
michael@0 | 199 | nsLayoutUtils::ComputeWidthValue(aRenderingContext, aFrame, |
michael@0 | 200 | 0, 0, 0, maxWidth); |
michael@0 | 201 | if (w < minCoord) |
michael@0 | 202 | minCoord = w; |
michael@0 | 203 | if (w < prefCoord) |
michael@0 | 204 | prefCoord = w; |
michael@0 | 205 | } else if (unit == eStyleUnit_Percent) { |
michael@0 | 206 | float p = stylePos->mMaxWidth.GetPercentValue(); |
michael@0 | 207 | if (p < prefPercent) |
michael@0 | 208 | prefPercent = p; |
michael@0 | 209 | } |
michael@0 | 210 | // treat calc() with percentages on max-width just like 'none'. |
michael@0 | 211 | |
michael@0 | 212 | nsStyleCoord minWidth(stylePos->mMinWidth); |
michael@0 | 213 | if (minWidth.GetUnit() == eStyleUnit_Enumerated) { |
michael@0 | 214 | if (!aIsCell || minWidth.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE) |
michael@0 | 215 | minWidth.SetCoordValue(0); |
michael@0 | 216 | else if (minWidth.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT) |
michael@0 | 217 | // for 'min-width', '-moz-fit-content' is like |
michael@0 | 218 | // '-moz-min-content' |
michael@0 | 219 | minWidth.SetIntValue(NS_STYLE_WIDTH_MIN_CONTENT, |
michael@0 | 220 | eStyleUnit_Enumerated); |
michael@0 | 221 | } |
michael@0 | 222 | unit = minWidth.GetUnit(); |
michael@0 | 223 | if (minWidth.ConvertsToLength() || unit == eStyleUnit_Enumerated) { |
michael@0 | 224 | nscoord w = |
michael@0 | 225 | nsLayoutUtils::ComputeWidthValue(aRenderingContext, aFrame, |
michael@0 | 226 | 0, 0, 0, minWidth); |
michael@0 | 227 | if (w > minCoord) |
michael@0 | 228 | minCoord = w; |
michael@0 | 229 | if (w > prefCoord) |
michael@0 | 230 | prefCoord = w; |
michael@0 | 231 | } else if (unit == eStyleUnit_Percent) { |
michael@0 | 232 | float p = stylePos->mMinWidth.GetPercentValue(); |
michael@0 | 233 | if (p > prefPercent) |
michael@0 | 234 | prefPercent = p; |
michael@0 | 235 | } |
michael@0 | 236 | // treat calc() with percentages on min-width just like '0'. |
michael@0 | 237 | |
michael@0 | 238 | // XXX Should col frame have border/padding considered? |
michael@0 | 239 | if (aIsCell) { |
michael@0 | 240 | minCoord += boxSizingToBorderEdge; |
michael@0 | 241 | prefCoord = NSCoordSaturatingAdd(prefCoord, boxSizingToBorderEdge); |
michael@0 | 242 | } |
michael@0 | 243 | |
michael@0 | 244 | return CellWidthInfo(minCoord, prefCoord, prefPercent, hasSpecifiedWidth); |
michael@0 | 245 | } |
michael@0 | 246 | |
michael@0 | 247 | static inline CellWidthInfo |
michael@0 | 248 | GetCellWidthInfo(nsRenderingContext *aRenderingContext, |
michael@0 | 249 | nsTableCellFrame *aCellFrame) |
michael@0 | 250 | { |
michael@0 | 251 | return GetWidthInfo(aRenderingContext, aCellFrame, true); |
michael@0 | 252 | } |
michael@0 | 253 | |
michael@0 | 254 | static inline CellWidthInfo |
michael@0 | 255 | GetColWidthInfo(nsRenderingContext *aRenderingContext, |
michael@0 | 256 | nsIFrame *aFrame) |
michael@0 | 257 | { |
michael@0 | 258 | return GetWidthInfo(aRenderingContext, aFrame, false); |
michael@0 | 259 | } |
michael@0 | 260 | |
michael@0 | 261 | |
michael@0 | 262 | /** |
michael@0 | 263 | * The algorithm in this function, in addition to meeting the |
michael@0 | 264 | * requirements of Web-compatibility, is also invariant under reordering |
michael@0 | 265 | * of the rows within a table (something that most, but not all, other |
michael@0 | 266 | * browsers are). |
michael@0 | 267 | */ |
michael@0 | 268 | void |
michael@0 | 269 | BasicTableLayoutStrategy::ComputeColumnIntrinsicWidths(nsRenderingContext* aRenderingContext) |
michael@0 | 270 | { |
michael@0 | 271 | nsTableFrame *tableFrame = mTableFrame; |
michael@0 | 272 | nsTableCellMap *cellMap = tableFrame->GetCellMap(); |
michael@0 | 273 | |
michael@0 | 274 | mozilla::AutoStackArena arena; |
michael@0 | 275 | SpanningCellSorter spanningCells; |
michael@0 | 276 | |
michael@0 | 277 | // Loop over the columns to consider the columns and cells *without* |
michael@0 | 278 | // a colspan. |
michael@0 | 279 | int32_t col, col_end; |
michael@0 | 280 | for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) { |
michael@0 | 281 | nsTableColFrame *colFrame = tableFrame->GetColFrame(col); |
michael@0 | 282 | if (!colFrame) { |
michael@0 | 283 | NS_ERROR("column frames out of sync with cell map"); |
michael@0 | 284 | continue; |
michael@0 | 285 | } |
michael@0 | 286 | colFrame->ResetIntrinsics(); |
michael@0 | 287 | colFrame->ResetSpanIntrinsics(); |
michael@0 | 288 | |
michael@0 | 289 | // Consider the widths on the column. |
michael@0 | 290 | CellWidthInfo colInfo = GetColWidthInfo(aRenderingContext, colFrame); |
michael@0 | 291 | colFrame->AddCoords(colInfo.minCoord, colInfo.prefCoord, |
michael@0 | 292 | colInfo.hasSpecifiedWidth); |
michael@0 | 293 | colFrame->AddPrefPercent(colInfo.prefPercent); |
michael@0 | 294 | |
michael@0 | 295 | // Consider the widths on the column-group. Note that we follow |
michael@0 | 296 | // what the HTML spec says here, and make the width apply to |
michael@0 | 297 | // each column in the group, not the group as a whole. |
michael@0 | 298 | |
michael@0 | 299 | // If column has width, column-group doesn't override width. |
michael@0 | 300 | if (colInfo.minCoord == 0 && colInfo.prefCoord == 0 && |
michael@0 | 301 | colInfo.prefPercent == 0.0f) { |
michael@0 | 302 | NS_ASSERTION(colFrame->GetParent()->GetType() == |
michael@0 | 303 | nsGkAtoms::tableColGroupFrame, |
michael@0 | 304 | "expected a column-group"); |
michael@0 | 305 | colInfo = GetColWidthInfo(aRenderingContext, colFrame->GetParent()); |
michael@0 | 306 | colFrame->AddCoords(colInfo.minCoord, colInfo.prefCoord, |
michael@0 | 307 | colInfo.hasSpecifiedWidth); |
michael@0 | 308 | colFrame->AddPrefPercent(colInfo.prefPercent); |
michael@0 | 309 | } |
michael@0 | 310 | |
michael@0 | 311 | // Consider the contents of and the widths on the cells without |
michael@0 | 312 | // colspans. |
michael@0 | 313 | nsCellMapColumnIterator columnIter(cellMap, col); |
michael@0 | 314 | int32_t row, colSpan; |
michael@0 | 315 | nsTableCellFrame* cellFrame; |
michael@0 | 316 | while ((cellFrame = columnIter.GetNextFrame(&row, &colSpan))) { |
michael@0 | 317 | if (colSpan > 1) { |
michael@0 | 318 | spanningCells.AddCell(colSpan, row, col); |
michael@0 | 319 | continue; |
michael@0 | 320 | } |
michael@0 | 321 | |
michael@0 | 322 | CellWidthInfo info = GetCellWidthInfo(aRenderingContext, cellFrame); |
michael@0 | 323 | |
michael@0 | 324 | colFrame->AddCoords(info.minCoord, info.prefCoord, |
michael@0 | 325 | info.hasSpecifiedWidth); |
michael@0 | 326 | colFrame->AddPrefPercent(info.prefPercent); |
michael@0 | 327 | } |
michael@0 | 328 | #ifdef DEBUG_dbaron_off |
michael@0 | 329 | printf("table %p col %d nonspan: min=%d pref=%d spec=%d pct=%f\n", |
michael@0 | 330 | mTableFrame, col, colFrame->GetMinCoord(), |
michael@0 | 331 | colFrame->GetPrefCoord(), colFrame->GetHasSpecifiedCoord(), |
michael@0 | 332 | colFrame->GetPrefPercent()); |
michael@0 | 333 | #endif |
michael@0 | 334 | } |
michael@0 | 335 | #ifdef DEBUG_TABLE_STRATEGY |
michael@0 | 336 | printf("ComputeColumnIntrinsicWidths single\n"); |
michael@0 | 337 | mTableFrame->Dump(false, true, false); |
michael@0 | 338 | #endif |
michael@0 | 339 | |
michael@0 | 340 | // Consider the cells with a colspan that we saved in the loop above |
michael@0 | 341 | // into the spanning cell sorter. We consider these cells by seeing |
michael@0 | 342 | // if they require adding to the widths resulting only from cells |
michael@0 | 343 | // with a smaller colspan, and therefore we must process them sorted |
michael@0 | 344 | // in increasing order by colspan. For each colspan group, we |
michael@0 | 345 | // accumulate new values to accumulate in the column frame's Span* |
michael@0 | 346 | // members. |
michael@0 | 347 | // |
michael@0 | 348 | // Considering things only relative to the widths resulting from |
michael@0 | 349 | // cells with smaller colspans (rather than incrementally including |
michael@0 | 350 | // the results from spanning cells, or doing spanning and |
michael@0 | 351 | // non-spanning cells in a single pass) means that layout remains |
michael@0 | 352 | // row-order-invariant and (except for percentage widths that add to |
michael@0 | 353 | // more than 100%) column-order invariant. |
michael@0 | 354 | // |
michael@0 | 355 | // Starting with smaller colspans makes it more likely that we |
michael@0 | 356 | // satisfy all the constraints given and don't distribute space to |
michael@0 | 357 | // columns where we don't need it. |
michael@0 | 358 | SpanningCellSorter::Item *item; |
michael@0 | 359 | int32_t colSpan; |
michael@0 | 360 | while ((item = spanningCells.GetNext(&colSpan))) { |
michael@0 | 361 | NS_ASSERTION(colSpan > 1, |
michael@0 | 362 | "cell should not have been put in spanning cell sorter"); |
michael@0 | 363 | do { |
michael@0 | 364 | int32_t row = item->row; |
michael@0 | 365 | col = item->col; |
michael@0 | 366 | CellData *cellData = cellMap->GetDataAt(row, col); |
michael@0 | 367 | NS_ASSERTION(cellData && cellData->IsOrig(), |
michael@0 | 368 | "bogus result from spanning cell sorter"); |
michael@0 | 369 | |
michael@0 | 370 | nsTableCellFrame *cellFrame = cellData->GetCellFrame(); |
michael@0 | 371 | NS_ASSERTION(cellFrame, "bogus result from spanning cell sorter"); |
michael@0 | 372 | |
michael@0 | 373 | CellWidthInfo info = GetCellWidthInfo(aRenderingContext, cellFrame); |
michael@0 | 374 | |
michael@0 | 375 | if (info.prefPercent > 0.0f) { |
michael@0 | 376 | DistributePctWidthToColumns(info.prefPercent, |
michael@0 | 377 | col, colSpan); |
michael@0 | 378 | } |
michael@0 | 379 | DistributeWidthToColumns(info.minCoord, col, colSpan, |
michael@0 | 380 | BTLS_MIN_WIDTH, info.hasSpecifiedWidth); |
michael@0 | 381 | DistributeWidthToColumns(info.prefCoord, col, colSpan, |
michael@0 | 382 | BTLS_PREF_WIDTH, info.hasSpecifiedWidth); |
michael@0 | 383 | } while ((item = item->next)); |
michael@0 | 384 | |
michael@0 | 385 | // Combine the results of the span analysis into the main results, |
michael@0 | 386 | // for each increment of colspan. |
michael@0 | 387 | |
michael@0 | 388 | for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) { |
michael@0 | 389 | nsTableColFrame *colFrame = tableFrame->GetColFrame(col); |
michael@0 | 390 | if (!colFrame) { |
michael@0 | 391 | NS_ERROR("column frames out of sync with cell map"); |
michael@0 | 392 | continue; |
michael@0 | 393 | } |
michael@0 | 394 | |
michael@0 | 395 | colFrame->AccumulateSpanIntrinsics(); |
michael@0 | 396 | colFrame->ResetSpanIntrinsics(); |
michael@0 | 397 | |
michael@0 | 398 | #ifdef DEBUG_dbaron_off |
michael@0 | 399 | printf("table %p col %d span %d: min=%d pref=%d spec=%d pct=%f\n", |
michael@0 | 400 | mTableFrame, col, colSpan, colFrame->GetMinCoord(), |
michael@0 | 401 | colFrame->GetPrefCoord(), colFrame->GetHasSpecifiedCoord(), |
michael@0 | 402 | colFrame->GetPrefPercent()); |
michael@0 | 403 | #endif |
michael@0 | 404 | } |
michael@0 | 405 | } |
michael@0 | 406 | |
michael@0 | 407 | // Prevent percentages from adding to more than 100% by (to be |
michael@0 | 408 | // compatible with other browsers) treating any percentages that would |
michael@0 | 409 | // increase the total percentage to more than 100% as the number that |
michael@0 | 410 | // would increase it to only 100% (which is 0% if we've already hit |
michael@0 | 411 | // 100%). This means layout depends on the order of columns. |
michael@0 | 412 | float pct_used = 0.0f; |
michael@0 | 413 | for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) { |
michael@0 | 414 | nsTableColFrame *colFrame = tableFrame->GetColFrame(col); |
michael@0 | 415 | if (!colFrame) { |
michael@0 | 416 | NS_ERROR("column frames out of sync with cell map"); |
michael@0 | 417 | continue; |
michael@0 | 418 | } |
michael@0 | 419 | |
michael@0 | 420 | colFrame->AdjustPrefPercent(&pct_used); |
michael@0 | 421 | } |
michael@0 | 422 | |
michael@0 | 423 | #ifdef DEBUG_TABLE_STRATEGY |
michael@0 | 424 | printf("ComputeColumnIntrinsicWidths spanning\n"); |
michael@0 | 425 | mTableFrame->Dump(false, true, false); |
michael@0 | 426 | #endif |
michael@0 | 427 | } |
michael@0 | 428 | |
michael@0 | 429 | void |
michael@0 | 430 | BasicTableLayoutStrategy::ComputeIntrinsicWidths(nsRenderingContext* aRenderingContext) |
michael@0 | 431 | { |
michael@0 | 432 | ComputeColumnIntrinsicWidths(aRenderingContext); |
michael@0 | 433 | |
michael@0 | 434 | nsTableCellMap *cellMap = mTableFrame->GetCellMap(); |
michael@0 | 435 | nscoord min = 0, pref = 0, max_small_pct_pref = 0, nonpct_pref_total = 0; |
michael@0 | 436 | float pct_total = 0.0f; // always from 0.0f - 1.0f |
michael@0 | 437 | int32_t colCount = cellMap->GetColCount(); |
michael@0 | 438 | nscoord spacing = mTableFrame->GetCellSpacingX(); |
michael@0 | 439 | nscoord add = spacing; // add (colcount + 1) * spacing for columns |
michael@0 | 440 | // where a cell originates |
michael@0 | 441 | |
michael@0 | 442 | for (int32_t col = 0; col < colCount; ++col) { |
michael@0 | 443 | nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); |
michael@0 | 444 | if (!colFrame) { |
michael@0 | 445 | NS_ERROR("column frames out of sync with cell map"); |
michael@0 | 446 | continue; |
michael@0 | 447 | } |
michael@0 | 448 | if (mTableFrame->ColumnHasCellSpacingBefore(col)) { |
michael@0 | 449 | add += spacing; |
michael@0 | 450 | } |
michael@0 | 451 | min += colFrame->GetMinCoord(); |
michael@0 | 452 | pref = NSCoordSaturatingAdd(pref, colFrame->GetPrefCoord()); |
michael@0 | 453 | |
michael@0 | 454 | // Percentages are of the table, so we have to reverse them for |
michael@0 | 455 | // intrinsic widths. |
michael@0 | 456 | float p = colFrame->GetPrefPercent(); |
michael@0 | 457 | if (p > 0.0f) { |
michael@0 | 458 | nscoord colPref = colFrame->GetPrefCoord(); |
michael@0 | 459 | nscoord new_small_pct_expand = |
michael@0 | 460 | (colPref == nscoord_MAX ? |
michael@0 | 461 | nscoord_MAX : nscoord(float(colPref) / p)); |
michael@0 | 462 | if (new_small_pct_expand > max_small_pct_pref) { |
michael@0 | 463 | max_small_pct_pref = new_small_pct_expand; |
michael@0 | 464 | } |
michael@0 | 465 | pct_total += p; |
michael@0 | 466 | } else { |
michael@0 | 467 | nonpct_pref_total = NSCoordSaturatingAdd(nonpct_pref_total, |
michael@0 | 468 | colFrame->GetPrefCoord()); |
michael@0 | 469 | } |
michael@0 | 470 | } |
michael@0 | 471 | |
michael@0 | 472 | nscoord pref_pct_expand = pref; |
michael@0 | 473 | |
michael@0 | 474 | // Account for small percentages expanding the preferred width of |
michael@0 | 475 | // *other* columns. |
michael@0 | 476 | if (max_small_pct_pref > pref_pct_expand) { |
michael@0 | 477 | pref_pct_expand = max_small_pct_pref; |
michael@0 | 478 | } |
michael@0 | 479 | |
michael@0 | 480 | // Account for large percentages expanding the preferred width of |
michael@0 | 481 | // themselves. There's no need to iterate over the columns multiple |
michael@0 | 482 | // times, since when there is such a need, the small percentage |
michael@0 | 483 | // effect is bigger anyway. (I think!) |
michael@0 | 484 | NS_ASSERTION(0.0f <= pct_total && pct_total <= 1.0f, |
michael@0 | 485 | "column percentage widths not adjusted down to 100%"); |
michael@0 | 486 | if (pct_total == 1.0f) { |
michael@0 | 487 | if (nonpct_pref_total > 0) { |
michael@0 | 488 | pref_pct_expand = nscoord_MAX; |
michael@0 | 489 | // XXX Or should I use some smaller value? (Test this using |
michael@0 | 490 | // nested tables!) |
michael@0 | 491 | } |
michael@0 | 492 | } else { |
michael@0 | 493 | nscoord large_pct_pref = |
michael@0 | 494 | (nonpct_pref_total == nscoord_MAX ? |
michael@0 | 495 | nscoord_MAX : |
michael@0 | 496 | nscoord(float(nonpct_pref_total) / (1.0f - pct_total))); |
michael@0 | 497 | if (large_pct_pref > pref_pct_expand) |
michael@0 | 498 | pref_pct_expand = large_pct_pref; |
michael@0 | 499 | } |
michael@0 | 500 | |
michael@0 | 501 | // border-spacing isn't part of the basis for percentages |
michael@0 | 502 | if (colCount > 0) { |
michael@0 | 503 | min += add; |
michael@0 | 504 | pref = NSCoordSaturatingAdd(pref, add); |
michael@0 | 505 | pref_pct_expand = NSCoordSaturatingAdd(pref_pct_expand, add); |
michael@0 | 506 | } |
michael@0 | 507 | |
michael@0 | 508 | mMinWidth = min; |
michael@0 | 509 | mPrefWidth = pref; |
michael@0 | 510 | mPrefWidthPctExpand = pref_pct_expand; |
michael@0 | 511 | } |
michael@0 | 512 | |
michael@0 | 513 | /* virtual */ void |
michael@0 | 514 | BasicTableLayoutStrategy::MarkIntrinsicWidthsDirty() |
michael@0 | 515 | { |
michael@0 | 516 | mMinWidth = NS_INTRINSIC_WIDTH_UNKNOWN; |
michael@0 | 517 | mPrefWidth = NS_INTRINSIC_WIDTH_UNKNOWN; |
michael@0 | 518 | mPrefWidthPctExpand = NS_INTRINSIC_WIDTH_UNKNOWN; |
michael@0 | 519 | mLastCalcWidth = nscoord_MIN; |
michael@0 | 520 | } |
michael@0 | 521 | |
michael@0 | 522 | /* virtual */ void |
michael@0 | 523 | BasicTableLayoutStrategy::ComputeColumnWidths(const nsHTMLReflowState& aReflowState) |
michael@0 | 524 | { |
michael@0 | 525 | nscoord width = aReflowState.ComputedWidth(); |
michael@0 | 526 | |
michael@0 | 527 | if (mLastCalcWidth == width) |
michael@0 | 528 | return; |
michael@0 | 529 | mLastCalcWidth = width; |
michael@0 | 530 | |
michael@0 | 531 | NS_ASSERTION((mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN) == |
michael@0 | 532 | (mPrefWidth == NS_INTRINSIC_WIDTH_UNKNOWN), |
michael@0 | 533 | "dirtyness out of sync"); |
michael@0 | 534 | NS_ASSERTION((mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN) == |
michael@0 | 535 | (mPrefWidthPctExpand == NS_INTRINSIC_WIDTH_UNKNOWN), |
michael@0 | 536 | "dirtyness out of sync"); |
michael@0 | 537 | // XXX Is this needed? |
michael@0 | 538 | if (mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN) |
michael@0 | 539 | ComputeIntrinsicWidths(aReflowState.rendContext); |
michael@0 | 540 | |
michael@0 | 541 | nsTableCellMap *cellMap = mTableFrame->GetCellMap(); |
michael@0 | 542 | int32_t colCount = cellMap->GetColCount(); |
michael@0 | 543 | if (colCount <= 0) |
michael@0 | 544 | return; // nothing to do |
michael@0 | 545 | |
michael@0 | 546 | DistributeWidthToColumns(width, 0, colCount, BTLS_FINAL_WIDTH, false); |
michael@0 | 547 | |
michael@0 | 548 | #ifdef DEBUG_TABLE_STRATEGY |
michael@0 | 549 | printf("ComputeColumnWidths final\n"); |
michael@0 | 550 | mTableFrame->Dump(false, true, false); |
michael@0 | 551 | #endif |
michael@0 | 552 | } |
michael@0 | 553 | |
michael@0 | 554 | void |
michael@0 | 555 | BasicTableLayoutStrategy::DistributePctWidthToColumns(float aSpanPrefPct, |
michael@0 | 556 | int32_t aFirstCol, |
michael@0 | 557 | int32_t aColCount) |
michael@0 | 558 | { |
michael@0 | 559 | // First loop to determine: |
michael@0 | 560 | int32_t nonPctColCount = 0; // number of spanned columns without % width |
michael@0 | 561 | nscoord nonPctTotalPrefWidth = 0; // total pref width of those columns |
michael@0 | 562 | // and to reduce aSpanPrefPct by columns that already have % width |
michael@0 | 563 | |
michael@0 | 564 | int32_t scol, scol_end; |
michael@0 | 565 | nsTableCellMap *cellMap = mTableFrame->GetCellMap(); |
michael@0 | 566 | for (scol = aFirstCol, scol_end = aFirstCol + aColCount; |
michael@0 | 567 | scol < scol_end; ++scol) { |
michael@0 | 568 | nsTableColFrame *scolFrame = mTableFrame->GetColFrame(scol); |
michael@0 | 569 | if (!scolFrame) { |
michael@0 | 570 | NS_ERROR("column frames out of sync with cell map"); |
michael@0 | 571 | continue; |
michael@0 | 572 | } |
michael@0 | 573 | float scolPct = scolFrame->GetPrefPercent(); |
michael@0 | 574 | if (scolPct == 0.0f) { |
michael@0 | 575 | nonPctTotalPrefWidth += scolFrame->GetPrefCoord(); |
michael@0 | 576 | if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) { |
michael@0 | 577 | ++nonPctColCount; |
michael@0 | 578 | } |
michael@0 | 579 | } else { |
michael@0 | 580 | aSpanPrefPct -= scolPct; |
michael@0 | 581 | } |
michael@0 | 582 | } |
michael@0 | 583 | |
michael@0 | 584 | if (aSpanPrefPct <= 0.0f || nonPctColCount == 0) { |
michael@0 | 585 | // There's no %-width on the colspan left over to distribute, |
michael@0 | 586 | // or there are no columns to which we could distribute %-width |
michael@0 | 587 | return; |
michael@0 | 588 | } |
michael@0 | 589 | |
michael@0 | 590 | // Second loop, to distribute what remains of aSpanPrefPct |
michael@0 | 591 | // between the non-percent-width spanned columns |
michael@0 | 592 | const bool spanHasNonPctPref = nonPctTotalPrefWidth > 0; // Loop invariant |
michael@0 | 593 | for (scol = aFirstCol, scol_end = aFirstCol + aColCount; |
michael@0 | 594 | scol < scol_end; ++scol) { |
michael@0 | 595 | nsTableColFrame *scolFrame = mTableFrame->GetColFrame(scol); |
michael@0 | 596 | if (!scolFrame) { |
michael@0 | 597 | NS_ERROR("column frames out of sync with cell map"); |
michael@0 | 598 | continue; |
michael@0 | 599 | } |
michael@0 | 600 | |
michael@0 | 601 | if (scolFrame->GetPrefPercent() == 0.0f) { |
michael@0 | 602 | NS_ASSERTION((!spanHasNonPctPref || |
michael@0 | 603 | nonPctTotalPrefWidth != 0) && |
michael@0 | 604 | nonPctColCount != 0, |
michael@0 | 605 | "should not be zero if we haven't allocated " |
michael@0 | 606 | "all pref percent"); |
michael@0 | 607 | |
michael@0 | 608 | float allocatedPct; // % width to be given to this column |
michael@0 | 609 | if (spanHasNonPctPref) { |
michael@0 | 610 | // Group so we're multiplying by 1.0f when we need |
michael@0 | 611 | // to use up aSpanPrefPct. |
michael@0 | 612 | allocatedPct = aSpanPrefPct * |
michael@0 | 613 | (float(scolFrame->GetPrefCoord()) / |
michael@0 | 614 | float(nonPctTotalPrefWidth)); |
michael@0 | 615 | } else if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) { |
michael@0 | 616 | // distribute equally when all pref widths are 0 |
michael@0 | 617 | allocatedPct = aSpanPrefPct / float(nonPctColCount); |
michael@0 | 618 | } else { |
michael@0 | 619 | allocatedPct = 0.0f; |
michael@0 | 620 | } |
michael@0 | 621 | // Allocate the percent |
michael@0 | 622 | scolFrame->AddSpanPrefPercent(allocatedPct); |
michael@0 | 623 | |
michael@0 | 624 | // To avoid accumulating rounding error from division, |
michael@0 | 625 | // subtract this column's values from the totals. |
michael@0 | 626 | aSpanPrefPct -= allocatedPct; |
michael@0 | 627 | nonPctTotalPrefWidth -= scolFrame->GetPrefCoord(); |
michael@0 | 628 | if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) { |
michael@0 | 629 | --nonPctColCount; |
michael@0 | 630 | } |
michael@0 | 631 | |
michael@0 | 632 | if (!aSpanPrefPct) { |
michael@0 | 633 | // No more span-percent-width to distribute --> we're done. |
michael@0 | 634 | NS_ASSERTION(spanHasNonPctPref ? |
michael@0 | 635 | nonPctTotalPrefWidth == 0 : |
michael@0 | 636 | nonPctColCount == 0, |
michael@0 | 637 | "No more pct width to distribute, but there are " |
michael@0 | 638 | "still cols that need some."); |
michael@0 | 639 | return; |
michael@0 | 640 | } |
michael@0 | 641 | } |
michael@0 | 642 | } |
michael@0 | 643 | } |
michael@0 | 644 | |
michael@0 | 645 | void |
michael@0 | 646 | BasicTableLayoutStrategy::DistributeWidthToColumns(nscoord aWidth, |
michael@0 | 647 | int32_t aFirstCol, |
michael@0 | 648 | int32_t aColCount, |
michael@0 | 649 | BtlsWidthType aWidthType, |
michael@0 | 650 | bool aSpanHasSpecifiedWidth) |
michael@0 | 651 | { |
michael@0 | 652 | NS_ASSERTION(aWidthType != BTLS_FINAL_WIDTH || |
michael@0 | 653 | (aFirstCol == 0 && |
michael@0 | 654 | aColCount == mTableFrame->GetCellMap()->GetColCount()), |
michael@0 | 655 | "Computing final column widths, but didn't get full column range"); |
michael@0 | 656 | |
michael@0 | 657 | // border-spacing isn't part of the basis for percentages. |
michael@0 | 658 | nscoord spacing = mTableFrame->GetCellSpacingX(); |
michael@0 | 659 | nscoord subtract = 0; |
michael@0 | 660 | // aWidth initially includes border-spacing for the boundaries in between |
michael@0 | 661 | // each of the columns. We start at aFirstCol + 1 because the first |
michael@0 | 662 | // in-between boundary would be at the left edge of column aFirstCol + 1 |
michael@0 | 663 | for (int32_t col = aFirstCol + 1; col < aFirstCol + aColCount; ++col) { |
michael@0 | 664 | if (mTableFrame->ColumnHasCellSpacingBefore(col)) { |
michael@0 | 665 | subtract += spacing; |
michael@0 | 666 | } |
michael@0 | 667 | } |
michael@0 | 668 | if (aWidthType == BTLS_FINAL_WIDTH) { |
michael@0 | 669 | // If we're computing final col-width, then aWidth initially includes |
michael@0 | 670 | // border spacing on the table's far left + far right edge, too. Need |
michael@0 | 671 | // to subtract those out, too. |
michael@0 | 672 | subtract += spacing * 2; |
michael@0 | 673 | } |
michael@0 | 674 | aWidth = NSCoordSaturatingSubtract(aWidth, subtract, nscoord_MAX); |
michael@0 | 675 | |
michael@0 | 676 | /* |
michael@0 | 677 | * The goal of this function is to distribute |aWidth| between the |
michael@0 | 678 | * columns by making an appropriate AddSpanCoords or SetFinalWidth |
michael@0 | 679 | * call for each column. (We call AddSpanCoords if we're |
michael@0 | 680 | * distributing a column-spanning cell's minimum or preferred width |
michael@0 | 681 | * to its spanned columns. We call SetFinalWidth if we're |
michael@0 | 682 | * distributing a table's final width to its columns.) |
michael@0 | 683 | * |
michael@0 | 684 | * The idea is to either assign one of the following sets of widths |
michael@0 | 685 | * or a weighted average of two adjacent sets of widths. It is not |
michael@0 | 686 | * possible to assign values smaller than the smallest set of |
michael@0 | 687 | * widths. However, see below for handling the case of assigning |
michael@0 | 688 | * values larger than the largest set of widths. From smallest to |
michael@0 | 689 | * largest, these are: |
michael@0 | 690 | * |
michael@0 | 691 | * 1. [guess_min] Assign all columns their min width. |
michael@0 | 692 | * |
michael@0 | 693 | * 2. [guess_min_pct] Assign all columns with percentage widths |
michael@0 | 694 | * their percentage width, and all other columns their min width. |
michael@0 | 695 | * |
michael@0 | 696 | * 3. [guess_min_spec] Assign all columns with percentage widths |
michael@0 | 697 | * their percentage width, all columns with specified coordinate |
michael@0 | 698 | * widths their pref width (since it doesn't matter whether it's the |
michael@0 | 699 | * largest contributor to the pref width that was the specified |
michael@0 | 700 | * contributor), and all other columns their min width. |
michael@0 | 701 | * |
michael@0 | 702 | * 4. [guess_pref] Assign all columns with percentage widths their |
michael@0 | 703 | * specified width, and all other columns their pref width. |
michael@0 | 704 | * |
michael@0 | 705 | * If |aWidth| is *larger* than what we would assign in (4), then we |
michael@0 | 706 | * expand the columns: |
michael@0 | 707 | * |
michael@0 | 708 | * a. if any columns without a specified coordinate width or |
michael@0 | 709 | * percent width have nonzero pref width, in proportion to pref |
michael@0 | 710 | * width [total_flex_pref] |
michael@0 | 711 | * |
michael@0 | 712 | * b. otherwise, if any columns without a specified coordinate |
michael@0 | 713 | * width or percent width, but with cells originating in them, |
michael@0 | 714 | * have zero pref width, equally between these |
michael@0 | 715 | * [numNonSpecZeroWidthCols] |
michael@0 | 716 | * |
michael@0 | 717 | * c. otherwise, if any columns without percent width have nonzero |
michael@0 | 718 | * pref width, in proportion to pref width [total_fixed_pref] |
michael@0 | 719 | * |
michael@0 | 720 | * d. otherwise, if any columns have nonzero percentage widths, in |
michael@0 | 721 | * proportion to the percentage widths [total_pct] |
michael@0 | 722 | * |
michael@0 | 723 | * e. otherwise, equally. |
michael@0 | 724 | */ |
michael@0 | 725 | |
michael@0 | 726 | // Loop #1 over the columns, to figure out the four values above so |
michael@0 | 727 | // we know which case we're dealing with. |
michael@0 | 728 | |
michael@0 | 729 | nscoord guess_min = 0, |
michael@0 | 730 | guess_min_pct = 0, |
michael@0 | 731 | guess_min_spec = 0, |
michael@0 | 732 | guess_pref = 0, |
michael@0 | 733 | total_flex_pref = 0, |
michael@0 | 734 | total_fixed_pref = 0; |
michael@0 | 735 | float total_pct = 0.0f; // 0.0f to 1.0f |
michael@0 | 736 | int32_t numInfiniteWidthCols = 0; |
michael@0 | 737 | int32_t numNonSpecZeroWidthCols = 0; |
michael@0 | 738 | |
michael@0 | 739 | int32_t col; |
michael@0 | 740 | nsTableCellMap *cellMap = mTableFrame->GetCellMap(); |
michael@0 | 741 | for (col = aFirstCol; col < aFirstCol + aColCount; ++col) { |
michael@0 | 742 | nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); |
michael@0 | 743 | if (!colFrame) { |
michael@0 | 744 | NS_ERROR("column frames out of sync with cell map"); |
michael@0 | 745 | continue; |
michael@0 | 746 | } |
michael@0 | 747 | nscoord min_width = colFrame->GetMinCoord(); |
michael@0 | 748 | guess_min += min_width; |
michael@0 | 749 | if (colFrame->GetPrefPercent() != 0.0f) { |
michael@0 | 750 | float pct = colFrame->GetPrefPercent(); |
michael@0 | 751 | total_pct += pct; |
michael@0 | 752 | nscoord val = nscoord(float(aWidth) * pct); |
michael@0 | 753 | if (val < min_width) |
michael@0 | 754 | val = min_width; |
michael@0 | 755 | guess_min_pct += val; |
michael@0 | 756 | guess_pref = NSCoordSaturatingAdd(guess_pref, val); |
michael@0 | 757 | } else { |
michael@0 | 758 | nscoord pref_width = colFrame->GetPrefCoord(); |
michael@0 | 759 | if (pref_width == nscoord_MAX) { |
michael@0 | 760 | ++numInfiniteWidthCols; |
michael@0 | 761 | } |
michael@0 | 762 | guess_pref = NSCoordSaturatingAdd(guess_pref, pref_width); |
michael@0 | 763 | guess_min_pct += min_width; |
michael@0 | 764 | if (colFrame->GetHasSpecifiedCoord()) { |
michael@0 | 765 | // we'll add on the rest of guess_min_spec outside the |
michael@0 | 766 | // loop |
michael@0 | 767 | nscoord delta = NSCoordSaturatingSubtract(pref_width, |
michael@0 | 768 | min_width, 0); |
michael@0 | 769 | guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, delta); |
michael@0 | 770 | total_fixed_pref = NSCoordSaturatingAdd(total_fixed_pref, |
michael@0 | 771 | pref_width); |
michael@0 | 772 | } else if (pref_width == 0) { |
michael@0 | 773 | if (cellMap->GetNumCellsOriginatingInCol(col) > 0) { |
michael@0 | 774 | ++numNonSpecZeroWidthCols; |
michael@0 | 775 | } |
michael@0 | 776 | } else { |
michael@0 | 777 | total_flex_pref = NSCoordSaturatingAdd(total_flex_pref, |
michael@0 | 778 | pref_width); |
michael@0 | 779 | } |
michael@0 | 780 | } |
michael@0 | 781 | } |
michael@0 | 782 | guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, guess_min_pct); |
michael@0 | 783 | |
michael@0 | 784 | // Determine what we're flexing: |
michael@0 | 785 | enum Loop2Type { |
michael@0 | 786 | FLEX_PCT_SMALL, // between (1) and (2) above |
michael@0 | 787 | FLEX_FIXED_SMALL, // between (2) and (3) above |
michael@0 | 788 | FLEX_FLEX_SMALL, // between (3) and (4) above |
michael@0 | 789 | FLEX_FLEX_LARGE, // greater than (4) above, case (a) |
michael@0 | 790 | FLEX_FLEX_LARGE_ZERO, // greater than (4) above, case (b) |
michael@0 | 791 | FLEX_FIXED_LARGE, // greater than (4) above, case (c) |
michael@0 | 792 | FLEX_PCT_LARGE, // greater than (4) above, case (d) |
michael@0 | 793 | FLEX_ALL_LARGE // greater than (4) above, case (e) |
michael@0 | 794 | }; |
michael@0 | 795 | |
michael@0 | 796 | Loop2Type l2t; |
michael@0 | 797 | // These are constants (over columns) for each case's math. We use |
michael@0 | 798 | // a pair of nscoords rather than a float so that we can subtract |
michael@0 | 799 | // each column's allocation so we avoid accumulating rounding error. |
michael@0 | 800 | nscoord space; // the amount of extra width to allocate |
michael@0 | 801 | union { |
michael@0 | 802 | nscoord c; |
michael@0 | 803 | float f; |
michael@0 | 804 | } basis; // the sum of the statistic over columns to divide it |
michael@0 | 805 | if (aWidth < guess_pref) { |
michael@0 | 806 | if (aWidthType != BTLS_FINAL_WIDTH && aWidth <= guess_min) { |
michael@0 | 807 | // Return early -- we don't have any extra space to distribute. |
michael@0 | 808 | return; |
michael@0 | 809 | } |
michael@0 | 810 | NS_ASSERTION(!(aWidthType == BTLS_FINAL_WIDTH && aWidth < guess_min), |
michael@0 | 811 | "Table width is less than the " |
michael@0 | 812 | "sum of its columns' min widths"); |
michael@0 | 813 | if (aWidth < guess_min_pct) { |
michael@0 | 814 | l2t = FLEX_PCT_SMALL; |
michael@0 | 815 | space = aWidth - guess_min; |
michael@0 | 816 | basis.c = guess_min_pct - guess_min; |
michael@0 | 817 | } else if (aWidth < guess_min_spec) { |
michael@0 | 818 | l2t = FLEX_FIXED_SMALL; |
michael@0 | 819 | space = aWidth - guess_min_pct; |
michael@0 | 820 | basis.c = NSCoordSaturatingSubtract(guess_min_spec, guess_min_pct, |
michael@0 | 821 | nscoord_MAX); |
michael@0 | 822 | } else { |
michael@0 | 823 | l2t = FLEX_FLEX_SMALL; |
michael@0 | 824 | space = aWidth - guess_min_spec; |
michael@0 | 825 | basis.c = NSCoordSaturatingSubtract(guess_pref, guess_min_spec, |
michael@0 | 826 | nscoord_MAX); |
michael@0 | 827 | } |
michael@0 | 828 | } else { |
michael@0 | 829 | space = NSCoordSaturatingSubtract(aWidth, guess_pref, nscoord_MAX); |
michael@0 | 830 | if (total_flex_pref > 0) { |
michael@0 | 831 | l2t = FLEX_FLEX_LARGE; |
michael@0 | 832 | basis.c = total_flex_pref; |
michael@0 | 833 | } else if (numNonSpecZeroWidthCols > 0) { |
michael@0 | 834 | l2t = FLEX_FLEX_LARGE_ZERO; |
michael@0 | 835 | basis.c = numNonSpecZeroWidthCols; |
michael@0 | 836 | } else if (total_fixed_pref > 0) { |
michael@0 | 837 | l2t = FLEX_FIXED_LARGE; |
michael@0 | 838 | basis.c = total_fixed_pref; |
michael@0 | 839 | } else if (total_pct > 0.0f) { |
michael@0 | 840 | l2t = FLEX_PCT_LARGE; |
michael@0 | 841 | basis.f = total_pct; |
michael@0 | 842 | } else { |
michael@0 | 843 | l2t = FLEX_ALL_LARGE; |
michael@0 | 844 | basis.c = aColCount; |
michael@0 | 845 | } |
michael@0 | 846 | } |
michael@0 | 847 | |
michael@0 | 848 | #ifdef DEBUG_dbaron_off |
michael@0 | 849 | printf("ComputeColumnWidths: %d columns in width %d,\n" |
michael@0 | 850 | " guesses=[%d,%d,%d,%d], totals=[%d,%d,%f],\n" |
michael@0 | 851 | " l2t=%d, space=%d, basis.c=%d\n", |
michael@0 | 852 | aColCount, aWidth, |
michael@0 | 853 | guess_min, guess_min_pct, guess_min_spec, guess_pref, |
michael@0 | 854 | total_flex_pref, total_fixed_pref, total_pct, |
michael@0 | 855 | l2t, space, basis.c); |
michael@0 | 856 | #endif |
michael@0 | 857 | |
michael@0 | 858 | for (col = aFirstCol; col < aFirstCol + aColCount; ++col) { |
michael@0 | 859 | nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); |
michael@0 | 860 | if (!colFrame) { |
michael@0 | 861 | NS_ERROR("column frames out of sync with cell map"); |
michael@0 | 862 | continue; |
michael@0 | 863 | } |
michael@0 | 864 | nscoord col_width; |
michael@0 | 865 | |
michael@0 | 866 | float pct = colFrame->GetPrefPercent(); |
michael@0 | 867 | if (pct != 0.0f) { |
michael@0 | 868 | col_width = nscoord(float(aWidth) * pct); |
michael@0 | 869 | nscoord col_min = colFrame->GetMinCoord(); |
michael@0 | 870 | if (col_width < col_min) |
michael@0 | 871 | col_width = col_min; |
michael@0 | 872 | } else { |
michael@0 | 873 | col_width = colFrame->GetPrefCoord(); |
michael@0 | 874 | } |
michael@0 | 875 | |
michael@0 | 876 | nscoord col_width_before_adjust = col_width; |
michael@0 | 877 | |
michael@0 | 878 | switch (l2t) { |
michael@0 | 879 | case FLEX_PCT_SMALL: |
michael@0 | 880 | col_width = col_width_before_adjust = colFrame->GetMinCoord(); |
michael@0 | 881 | if (pct != 0.0f) { |
michael@0 | 882 | nscoord pct_minus_min = |
michael@0 | 883 | nscoord(float(aWidth) * pct) - col_width; |
michael@0 | 884 | if (pct_minus_min > 0) { |
michael@0 | 885 | float c = float(space) / float(basis.c); |
michael@0 | 886 | basis.c -= pct_minus_min; |
michael@0 | 887 | col_width += NSToCoordRound(float(pct_minus_min) * c); |
michael@0 | 888 | } |
michael@0 | 889 | } |
michael@0 | 890 | break; |
michael@0 | 891 | case FLEX_FIXED_SMALL: |
michael@0 | 892 | if (pct == 0.0f) { |
michael@0 | 893 | NS_ASSERTION(col_width == colFrame->GetPrefCoord(), |
michael@0 | 894 | "wrong width assigned"); |
michael@0 | 895 | if (colFrame->GetHasSpecifiedCoord()) { |
michael@0 | 896 | nscoord col_min = colFrame->GetMinCoord(); |
michael@0 | 897 | nscoord pref_minus_min = col_width - col_min; |
michael@0 | 898 | col_width = col_width_before_adjust = col_min; |
michael@0 | 899 | if (pref_minus_min != 0) { |
michael@0 | 900 | float c = float(space) / float(basis.c); |
michael@0 | 901 | basis.c -= pref_minus_min; |
michael@0 | 902 | col_width += NSToCoordRound( |
michael@0 | 903 | float(pref_minus_min) * c); |
michael@0 | 904 | } |
michael@0 | 905 | } else |
michael@0 | 906 | col_width = col_width_before_adjust = |
michael@0 | 907 | colFrame->GetMinCoord(); |
michael@0 | 908 | } |
michael@0 | 909 | break; |
michael@0 | 910 | case FLEX_FLEX_SMALL: |
michael@0 | 911 | if (pct == 0.0f && |
michael@0 | 912 | !colFrame->GetHasSpecifiedCoord()) { |
michael@0 | 913 | NS_ASSERTION(col_width == colFrame->GetPrefCoord(), |
michael@0 | 914 | "wrong width assigned"); |
michael@0 | 915 | nscoord col_min = colFrame->GetMinCoord(); |
michael@0 | 916 | nscoord pref_minus_min = |
michael@0 | 917 | NSCoordSaturatingSubtract(col_width, col_min, 0); |
michael@0 | 918 | col_width = col_width_before_adjust = col_min; |
michael@0 | 919 | if (pref_minus_min != 0) { |
michael@0 | 920 | float c = float(space) / float(basis.c); |
michael@0 | 921 | // If we have infinite-width cols, then the standard |
michael@0 | 922 | // adjustment to col_width using 'c' won't work, |
michael@0 | 923 | // because basis.c and pref_minus_min are both |
michael@0 | 924 | // nscoord_MAX and will cancel each other out in the |
michael@0 | 925 | // col_width adjustment (making us assign all the |
michael@0 | 926 | // space to the first inf-width col). To correct for |
michael@0 | 927 | // this, we'll also divide by numInfiniteWidthCols to |
michael@0 | 928 | // spread the space equally among the inf-width cols. |
michael@0 | 929 | if (numInfiniteWidthCols) { |
michael@0 | 930 | if (colFrame->GetPrefCoord() == nscoord_MAX) { |
michael@0 | 931 | c = c / float(numInfiniteWidthCols); |
michael@0 | 932 | --numInfiniteWidthCols; |
michael@0 | 933 | } else { |
michael@0 | 934 | c = 0.0f; |
michael@0 | 935 | } |
michael@0 | 936 | } |
michael@0 | 937 | basis.c = NSCoordSaturatingSubtract(basis.c, |
michael@0 | 938 | pref_minus_min, |
michael@0 | 939 | nscoord_MAX); |
michael@0 | 940 | col_width += NSToCoordRound( |
michael@0 | 941 | float(pref_minus_min) * c); |
michael@0 | 942 | } |
michael@0 | 943 | } |
michael@0 | 944 | break; |
michael@0 | 945 | case FLEX_FLEX_LARGE: |
michael@0 | 946 | if (pct == 0.0f && |
michael@0 | 947 | !colFrame->GetHasSpecifiedCoord()) { |
michael@0 | 948 | NS_ASSERTION(col_width == colFrame->GetPrefCoord(), |
michael@0 | 949 | "wrong width assigned"); |
michael@0 | 950 | if (col_width != 0) { |
michael@0 | 951 | if (space == nscoord_MAX) { |
michael@0 | 952 | basis.c -= col_width; |
michael@0 | 953 | col_width = nscoord_MAX; |
michael@0 | 954 | } else { |
michael@0 | 955 | float c = float(space) / float(basis.c); |
michael@0 | 956 | basis.c -= col_width; |
michael@0 | 957 | col_width += NSToCoordRound(float(col_width) * c); |
michael@0 | 958 | } |
michael@0 | 959 | } |
michael@0 | 960 | } |
michael@0 | 961 | break; |
michael@0 | 962 | case FLEX_FLEX_LARGE_ZERO: |
michael@0 | 963 | if (pct == 0.0f && |
michael@0 | 964 | !colFrame->GetHasSpecifiedCoord() && |
michael@0 | 965 | cellMap->GetNumCellsOriginatingInCol(col) > 0) { |
michael@0 | 966 | |
michael@0 | 967 | NS_ASSERTION(col_width == 0 && |
michael@0 | 968 | colFrame->GetPrefCoord() == 0, |
michael@0 | 969 | "Since we're in FLEX_FLEX_LARGE_ZERO case, " |
michael@0 | 970 | "all auto-width cols should have zero pref " |
michael@0 | 971 | "width."); |
michael@0 | 972 | float c = float(space) / float(basis.c); |
michael@0 | 973 | col_width += NSToCoordRound(c); |
michael@0 | 974 | --basis.c; |
michael@0 | 975 | } |
michael@0 | 976 | break; |
michael@0 | 977 | case FLEX_FIXED_LARGE: |
michael@0 | 978 | if (pct == 0.0f) { |
michael@0 | 979 | NS_ASSERTION(col_width == colFrame->GetPrefCoord(), |
michael@0 | 980 | "wrong width assigned"); |
michael@0 | 981 | NS_ASSERTION(colFrame->GetHasSpecifiedCoord() || |
michael@0 | 982 | colFrame->GetPrefCoord() == 0, |
michael@0 | 983 | "wrong case"); |
michael@0 | 984 | if (col_width != 0) { |
michael@0 | 985 | float c = float(space) / float(basis.c); |
michael@0 | 986 | basis.c -= col_width; |
michael@0 | 987 | col_width += NSToCoordRound(float(col_width) * c); |
michael@0 | 988 | } |
michael@0 | 989 | } |
michael@0 | 990 | break; |
michael@0 | 991 | case FLEX_PCT_LARGE: |
michael@0 | 992 | NS_ASSERTION(pct != 0.0f || colFrame->GetPrefCoord() == 0, |
michael@0 | 993 | "wrong case"); |
michael@0 | 994 | if (pct != 0.0f) { |
michael@0 | 995 | float c = float(space) / basis.f; |
michael@0 | 996 | col_width += NSToCoordRound(pct * c); |
michael@0 | 997 | basis.f -= pct; |
michael@0 | 998 | } |
michael@0 | 999 | break; |
michael@0 | 1000 | case FLEX_ALL_LARGE: |
michael@0 | 1001 | { |
michael@0 | 1002 | float c = float(space) / float(basis.c); |
michael@0 | 1003 | col_width += NSToCoordRound(c); |
michael@0 | 1004 | --basis.c; |
michael@0 | 1005 | } |
michael@0 | 1006 | break; |
michael@0 | 1007 | } |
michael@0 | 1008 | |
michael@0 | 1009 | // Only subtract from space if it's a real number. |
michael@0 | 1010 | if (space != nscoord_MAX) { |
michael@0 | 1011 | NS_ASSERTION(col_width != nscoord_MAX, |
michael@0 | 1012 | "How is col_width nscoord_MAX if space isn't?"); |
michael@0 | 1013 | NS_ASSERTION(col_width_before_adjust != nscoord_MAX, |
michael@0 | 1014 | "How is col_width_before_adjust nscoord_MAX if space isn't?"); |
michael@0 | 1015 | space -= col_width - col_width_before_adjust; |
michael@0 | 1016 | } |
michael@0 | 1017 | |
michael@0 | 1018 | NS_ASSERTION(col_width >= colFrame->GetMinCoord(), |
michael@0 | 1019 | "assigned width smaller than min"); |
michael@0 | 1020 | |
michael@0 | 1021 | // Apply the new width |
michael@0 | 1022 | switch (aWidthType) { |
michael@0 | 1023 | case BTLS_MIN_WIDTH: |
michael@0 | 1024 | { |
michael@0 | 1025 | // Note: AddSpanCoords requires both a min and pref width. |
michael@0 | 1026 | // For the pref width, we'll just pass in our computed |
michael@0 | 1027 | // min width, because the real pref width will be at least |
michael@0 | 1028 | // as big |
michael@0 | 1029 | colFrame->AddSpanCoords(col_width, col_width, |
michael@0 | 1030 | aSpanHasSpecifiedWidth); |
michael@0 | 1031 | } |
michael@0 | 1032 | break; |
michael@0 | 1033 | case BTLS_PREF_WIDTH: |
michael@0 | 1034 | { |
michael@0 | 1035 | // Note: AddSpanCoords requires both a min and pref width. |
michael@0 | 1036 | // For the min width, we'll just pass in 0, because |
michael@0 | 1037 | // the real min width will be at least 0 |
michael@0 | 1038 | colFrame->AddSpanCoords(0, col_width, |
michael@0 | 1039 | aSpanHasSpecifiedWidth); |
michael@0 | 1040 | } |
michael@0 | 1041 | break; |
michael@0 | 1042 | case BTLS_FINAL_WIDTH: |
michael@0 | 1043 | { |
michael@0 | 1044 | nscoord old_final = colFrame->GetFinalWidth(); |
michael@0 | 1045 | colFrame->SetFinalWidth(col_width); |
michael@0 | 1046 | |
michael@0 | 1047 | if (old_final != col_width) |
michael@0 | 1048 | mTableFrame->DidResizeColumns(); |
michael@0 | 1049 | } |
michael@0 | 1050 | break; |
michael@0 | 1051 | } |
michael@0 | 1052 | } |
michael@0 | 1053 | NS_ASSERTION((space == 0 || space == nscoord_MAX) && |
michael@0 | 1054 | ((l2t == FLEX_PCT_LARGE) |
michael@0 | 1055 | ? (-0.001f < basis.f && basis.f < 0.001f) |
michael@0 | 1056 | : (basis.c == 0 || basis.c == nscoord_MAX)), |
michael@0 | 1057 | "didn't subtract all that we added"); |
michael@0 | 1058 | } |