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