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