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