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/. */
8 * Web-compatible algorithms that determine column and table widths,
9 * used for CSS2's 'table-layout: auto'.
12 #include "BasicTableLayoutStrategy.h"
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();
36 BasicTableLayoutStrategy::~BasicTableLayoutStrategy() = default;
39 nscoord
BasicTableLayoutStrategy::GetMinISize(gfxContext
* aRenderingContext
) {
40 DISPLAY_MIN_INLINE_SIZE(mTableFrame
, mMinISize
);
41 if (mMinISize
== NS_INTRINSIC_ISIZE_UNKNOWN
) {
42 ComputeIntrinsicISizes(aRenderingContext
);
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
),
65 prefCoord(aPrefCoord
),
66 prefPercent(aPrefPercent
) {}
68 bool hasSpecifiedISize
;
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
,
79 nscoord minCoord
, prefCoord
;
80 const nsStylePosition
* stylePos
= aFrame
->StylePosition();
82 aFrame
->PresContext()->CompatibilityMode() == eCompatibility_NavQuirks
;
83 nscoord boxSizingToBorderEdge
= 0;
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
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
;
111 // StyleBoxSizing::Border and standards-mode
112 minCoord
+= offsets
.padding
+ offsets
.border
;
113 prefCoord
+= offsets
.padding
+ offsets
.border
;
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
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
)) {
139 prefCoord
= std::max(c
, minCoord
);
140 } else if (iSize
.ConvertsToPercentage()) {
141 prefPercent
= iSize
.ToPercentage();
142 } else if (aIsCell
) {
144 case StyleSize::Tag::MaxContent
:
145 // 'inline-size' only affects pref isize, not min
146 // isize, so don't change anything
148 case StyleSize::Tag::MinContent
:
149 prefCoord
= minCoord
;
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
:
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
)) {
176 ->ComputeISizeValue(aRenderingContext
, aWM
, zeroSize
,
177 zeroSize
, 0, maxISize
)
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
) {
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
)) {
201 ->ComputeISizeValue(aRenderingContext
, aWM
, zeroSize
,
202 zeroSize
, 0, minISize
)
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
) {
213 // XXX Should col frame have border/padding considered?
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
,
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
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*
250 int32_t col
, col_end
;
251 for (col
= 0, col_end
= cellMap
->GetColCount(); col
< col_end
; ++col
) {
252 nsTableColFrame
* colFrame
= tableFrame
->GetColFrame(col
);
254 NS_ERROR("column frames out of sync with cell map");
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
283 nsCellMapColumnIterator
columnIter(cellMap
, col
);
284 int32_t row
, colSpan
;
285 nsTableCellFrame
* cellFrame
;
286 while ((cellFrame
= columnIter
.GetNextFrame(&row
, &colSpan
))) {
288 spanningCells
.AddCell(colSpan
, row
, col
);
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());
304 #ifdef DEBUG_TABLE_STRATEGY
305 printf("ComputeColumnIntrinsicISizes single\n");
306 mTableFrame
->Dump(false, true, false);
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*
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
;
329 while ((item
= spanningCells
.GetNext(&colSpan
))) {
330 NS_ASSERTION(colSpan
> 1,
331 "cell should not have been put in spanning cell sorter");
333 int32_t row
= item
->row
;
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
);
359 NS_ERROR("column frames out of sync with cell map");
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());
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
);
384 NS_ERROR("column frames out of sync with cell map");
388 colFrame
->AdjustPrefPercent(&pct_used
);
391 #ifdef DEBUG_TABLE_STRATEGY
392 printf("ComputeColumnIntrinsicISizes spanning\n");
393 mTableFrame
->Dump(false, true, false);
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
407 nscoord add
= mTableFrame
->GetColSpacing(colCount
);
409 for (int32_t col
= 0; col
< colCount
; ++col
) {
410 nsTableColFrame
* colFrame
= mTableFrame
->GetColFrame(col
);
412 NS_ERROR("column frames out of sync with cell map");
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
423 float p
= colFrame
->GetPrefPercent();
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
;
434 NSCoordSaturatingAdd(nonpct_pref_total
, colFrame
->GetPrefCoord());
438 nscoord pref_pct_expand
= pref
;
440 // Account for small percentages expanding the preferred isize of
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
459 nscoord large_pct_pref
=
460 (nonpct_pref_total
== 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
469 pref
= NSCoordSaturatingAdd(pref
, add
);
470 pref_pct_expand
= NSCoordSaturatingAdd(pref_pct_expand
, add
);
475 mPrefISizePctExpand
= pref_pct_expand
;
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
;
487 void BasicTableLayoutStrategy::ComputeColumnISizes(
488 const ReflowInput
& aReflowInput
) {
489 nscoord iSize
= aReflowInput
.ComputedISize();
491 if (mLastCalcISize
== iSize
) {
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);
519 void BasicTableLayoutStrategy::DistributePctISizeToColumns(float aSpanPrefPct
,
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
;
531 nsTableColFrame
* scolFrame
= mTableFrame
->GetColFrame(scol
);
533 NS_ERROR("column frames out of sync with cell map");
536 float scolPct
= scolFrame
->GetPrefPercent();
537 if (scolPct
== 0.0f
) {
538 nonPctTotalPrefISize
+= scolFrame
->GetPrefCoord();
539 if (cellMap
->GetNumCellsOriginatingInCol(scol
) > 0) {
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
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
;
558 nsTableColFrame
* scolFrame
= mTableFrame
->GetColFrame(scol
);
560 NS_ERROR("column frames out of sync with cell map");
564 if (scolFrame
->GetPrefPercent() == 0.0f
) {
565 NS_ASSERTION((!spanHasNonPctPref
|| nonPctTotalPrefISize
!= 0) &&
567 "should not be zero if we haven't allocated "
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
);
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) {
594 // No more span-percent-isize to distribute --> we're done.
596 spanHasNonPctPref
? nonPctTotalPrefISize
== 0 : nonPctColCount
== 0,
597 "No more pct inline-size to distribute, "
598 "but there are still cols that need some.");
605 void BasicTableLayoutStrategy::DistributeISizeToColumns(
606 nscoord aISize
, int32_t aFirstCol
, int32_t aColCount
,
607 BtlsISizeType aISizeType
, bool aSpanHasSpecifiedISize
) {
609 aISizeType
!= BTLS_FINAL_ISIZE
||
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;
693 nsTableCellMap
* cellMap
= mTableFrame
->GetCellMap();
694 for (col
= aFirstCol
; col
< aFirstCol
+ aColCount
; ++col
) {
695 nsTableColFrame
* colFrame
= mTableFrame
->GetColFrame(col
);
697 NS_ERROR("column frames out of sync with cell map");
700 nscoord min_iSize
= colFrame
->GetMinCoord();
701 guess_min
+= min_iSize
;
702 if (colFrame
->GetPrefPercent() != 0.0f
) {
703 float pct
= colFrame
->GetPrefPercent();
705 nscoord val
= nscoord(float(aISize
) * pct
);
706 if (val
< min_iSize
) {
709 guess_min_pct
= NSCoordSaturatingAdd(guess_min_pct
, val
);
710 guess_pref
= NSCoordSaturatingAdd(guess_pref
, val
);
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
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
;
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:
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)
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
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.
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
;
772 NSCoordSaturatingSubtract(guess_min_spec
, guess_min_pct
, nscoord_MAX
);
774 l2t
= FLEX_FLEX_SMALL
;
775 space
= aISize
- guess_min_spec
;
777 NSCoordSaturatingSubtract(guess_pref
, guess_min_spec
, nscoord_MAX
);
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
;
794 l2t
= FLEX_ALL_LARGE
;
799 #ifdef DEBUG_dbaron_off
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
);
808 for (col
= aFirstCol
; col
< aFirstCol
+ aColCount
; ++col
) {
809 nsTableColFrame
* colFrame
= mTableFrame
->GetColFrame(col
);
811 NS_ERROR("column frames out of sync with cell map");
816 float pct
= colFrame
->GetPrefPercent();
818 col_iSize
= nscoord(float(aISize
) * pct
);
819 nscoord col_min
= colFrame
->GetMinCoord();
820 if (col_iSize
< col_min
) {
824 col_iSize
= colFrame
->GetPrefCoord();
827 nscoord col_iSize_before_adjust
= col_iSize
;
831 col_iSize
= col_iSize_before_adjust
= colFrame
->GetMinCoord();
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
));
842 case FLEX_FIXED_SMALL
:
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
,
854 col_iSize
= NSCoordSaturatingAdd(
855 col_iSize
, NSToCoordRound(float(pref_minus_min
) * c
));
858 col_iSize
= col_iSize_before_adjust
= colFrame
->GetMinCoord();
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
;
888 NSCoordSaturatingSubtract(basis
.c
, pref_minus_min
, nscoord_MAX
);
889 col_iSize
= NSCoordSaturatingAdd(
890 col_iSize
, NSToCoordRound(float(pref_minus_min
) * c
));
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
;
903 float c
= float(space
) / float(basis
.c
);
905 NSCoordSaturatingSubtract(basis
.c
, col_iSize
, nscoord_MAX
);
906 col_iSize
= NSCoordSaturatingAdd(
907 col_iSize
, NSToCoordRound(float(col_iSize
) * c
));
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
);
924 case FLEX_FIXED_LARGE
:
926 NS_ASSERTION(col_iSize
== colFrame
->GetPrefCoord(),
927 "wrong inline-size assigned");
929 colFrame
->GetHasSpecifiedCoord() || colFrame
->GetPrefCoord() == 0,
931 if (col_iSize
!= 0) {
932 float c
= float(space
) / float(basis
.c
);
934 NSCoordSaturatingSubtract(basis
.c
, col_iSize
, nscoord_MAX
);
935 col_iSize
= NSCoordSaturatingAdd(
936 col_iSize
, NSToCoordRound(float(col_iSize
) * c
));
941 NS_ASSERTION(pct
!= 0.0f
|| colFrame
->GetPrefCoord() == 0,
944 float c
= float(space
) / basis
.f
;
945 col_iSize
= NSCoordSaturatingAdd(col_iSize
, NSToCoordRound(pct
* c
));
949 case FLEX_ALL_LARGE
: {
950 float c
= float(space
) / float(basis
.c
);
951 col_iSize
= NSCoordSaturatingAdd(col_iSize
, NSToCoordRound(c
));
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?");
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
976 colFrame
->AddSpanCoords(col_iSize
, col_iSize
, aSpanHasSpecifiedISize
);
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
);
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();
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");