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 if (mMinISize
== NS_INTRINSIC_ISIZE_UNKNOWN
) {
41 ComputeIntrinsicISizes(aRenderingContext
);
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
),
63 prefCoord(aPrefCoord
),
64 prefPercent(aPrefPercent
) {}
66 bool hasSpecifiedISize
;
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
,
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();
83 aFrame
->PresContext()->CompatibilityMode() == eCompatibility_NavQuirks
;
84 nscoord boxSizingToBorderEdge
= 0;
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()
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
;
126 // StyleBoxSizing::Border
127 minCoord
+= offsets
.padding
+ offsets
.border
;
128 prefCoord
+= offsets
.padding
+ offsets
.border
;
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
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
)) {
154 prefCoord
= std::max(c
, minCoord
);
155 } else if (iSize
.ConvertsToPercentage()) {
156 prefPercent
= iSize
.ToPercentage();
157 } else if (aIsCell
) {
159 case StyleSize::Tag::MaxContent
:
160 // 'inline-size' only affects pref isize, not min
161 // isize, so don't change anything
163 case StyleSize::Tag::MinContent
:
164 prefCoord
= minCoord
;
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
:
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
)) {
195 aRenderingContext
, aWM
, zeroSize
, zeroSize
, 0, maxISize
,
196 stylePos
->BSize(aWM
), aFrame
->GetAspectRatio())
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
) {
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
)) {
221 aRenderingContext
, aWM
, zeroSize
, zeroSize
, 0, minISize
,
222 stylePos
->BSize(aWM
), aFrame
->GetAspectRatio())
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
) {
233 // XXX Should col frame have border/padding considered?
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
,
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
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*
270 int32_t col
, col_end
;
271 for (col
= 0, col_end
= cellMap
->GetColCount(); col
< col_end
; ++col
) {
272 nsTableColFrame
* colFrame
= tableFrame
->GetColFrame(col
);
274 NS_ERROR("column frames out of sync with cell map");
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
303 nsCellMapColumnIterator
columnIter(cellMap
, col
);
304 int32_t row
, colSpan
;
305 nsTableCellFrame
* cellFrame
;
306 while ((cellFrame
= columnIter
.GetNextFrame(&row
, &colSpan
))) {
308 spanningCells
.AddCell(colSpan
, row
, col
);
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());
324 #ifdef DEBUG_TABLE_STRATEGY
325 printf("ComputeColumnIntrinsicISizes single\n");
326 mTableFrame
->Dump(false, true, false);
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*
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
;
349 while ((item
= spanningCells
.GetNext(&colSpan
))) {
350 NS_ASSERTION(colSpan
> 1,
351 "cell should not have been put in spanning cell sorter");
353 int32_t row
= item
->row
;
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
);
380 NS_ERROR("column frames out of sync with cell map");
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());
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
);
405 NS_ERROR("column frames out of sync with cell map");
409 colFrame
->AdjustPrefPercent(&pct_used
);
412 #ifdef DEBUG_TABLE_STRATEGY
413 printf("ComputeColumnIntrinsicISizes spanning\n");
414 mTableFrame
->Dump(false, true, false);
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
428 nscoord add
= mTableFrame
->GetColSpacing(colCount
);
430 for (int32_t col
= 0; col
< colCount
; ++col
) {
431 nsTableColFrame
* colFrame
= mTableFrame
->GetColFrame(col
);
433 NS_ERROR("column frames out of sync with cell map");
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
444 float p
= colFrame
->GetPrefPercent();
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
;
455 NSCoordSaturatingAdd(nonpct_pref_total
, colFrame
->GetPrefCoord());
459 nscoord pref_pct_expand
= pref
;
461 // Account for small percentages expanding the preferred isize of
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
480 nscoord large_pct_pref
=
481 (nonpct_pref_total
== 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
490 pref
= NSCoordSaturatingAdd(pref
, add
);
491 pref_pct_expand
= NSCoordSaturatingAdd(pref_pct_expand
, add
);
496 mPrefISizePctExpand
= pref_pct_expand
;
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
;
508 void BasicTableLayoutStrategy::ComputeColumnISizes(
509 const ReflowInput
& aReflowInput
) {
510 nscoord iSize
= aReflowInput
.ComputedISize();
512 if (mLastCalcISize
== iSize
) {
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
,
535 #ifdef DEBUG_TABLE_STRATEGY
536 printf("ComputeColumnISizes final\n");
537 mTableFrame
->Dump(false, true, false);
541 void BasicTableLayoutStrategy::DistributePctISizeToColumns(float aSpanPrefPct
,
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
;
553 nsTableColFrame
* scolFrame
= mTableFrame
->GetColFrame(scol
);
555 NS_ERROR("column frames out of sync with cell map");
558 float scolPct
= scolFrame
->GetPrefPercent();
559 if (scolPct
== 0.0f
) {
560 nonPctTotalPrefISize
+= scolFrame
->GetPrefCoord();
561 if (cellMap
->GetNumCellsOriginatingInCol(scol
) > 0) {
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
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
;
580 nsTableColFrame
* scolFrame
= mTableFrame
->GetColFrame(scol
);
582 NS_ERROR("column frames out of sync with cell map");
586 if (scolFrame
->GetPrefPercent() == 0.0f
) {
587 NS_ASSERTION((!spanHasNonPctPref
|| nonPctTotalPrefISize
!= 0) &&
589 "should not be zero if we haven't allocated "
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
);
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) {
616 // No more span-percent-isize to distribute --> we're done.
618 spanHasNonPctPref
? nonPctTotalPrefISize
== 0 : nonPctColCount
== 0,
619 "No more pct inline-size to distribute, "
620 "but there are still cols that need some.");
627 void BasicTableLayoutStrategy::DistributeISizeToColumns(
628 nscoord aISize
, int32_t aFirstCol
, int32_t aColCount
,
629 BtlsISizeType aISizeType
, bool aSpanHasSpecifiedISize
) {
631 aISizeType
!= BtlsISizeType::FinalISize
||
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;
715 nsTableCellMap
* cellMap
= mTableFrame
->GetCellMap();
716 for (col
= aFirstCol
; col
< aFirstCol
+ aColCount
; ++col
) {
717 nsTableColFrame
* colFrame
= mTableFrame
->GetColFrame(col
);
719 NS_ERROR("column frames out of sync with cell map");
722 nscoord min_iSize
= colFrame
->GetMinCoord();
723 guess_min
+= min_iSize
;
724 if (colFrame
->GetPrefPercent() != 0.0f
) {
725 float pct
= colFrame
->GetPrefPercent();
727 nscoord val
= nscoord(float(aISize
) * pct
);
728 if (val
< min_iSize
) {
731 guess_min_pct
= NSCoordSaturatingAdd(guess_min_pct
, val
);
732 guess_pref
= NSCoordSaturatingAdd(guess_pref
, val
);
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
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
;
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:
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)
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
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.
784 !(aISizeType
== BtlsISizeType::FinalISize
&& aISize
< guess_min
),
785 "Table inline-size is less than the sum of its columns' min "
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
;
795 NSCoordSaturatingSubtract(guess_min_spec
, guess_min_pct
, nscoord_MAX
);
797 l2t
= FLEX_FLEX_SMALL
;
798 space
= aISize
- guess_min_spec
;
800 NSCoordSaturatingSubtract(guess_pref
, guess_min_spec
, nscoord_MAX
);
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
;
817 l2t
= FLEX_ALL_LARGE
;
822 #ifdef DEBUG_dbaron_off
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
);
831 for (col
= aFirstCol
; col
< aFirstCol
+ aColCount
; ++col
) {
832 nsTableColFrame
* colFrame
= mTableFrame
->GetColFrame(col
);
834 NS_ERROR("column frames out of sync with cell map");
839 float pct
= colFrame
->GetPrefPercent();
841 col_iSize
= nscoord(float(aISize
) * pct
);
842 nscoord col_min
= colFrame
->GetMinCoord();
843 if (col_iSize
< col_min
) {
847 col_iSize
= colFrame
->GetPrefCoord();
850 nscoord col_iSize_before_adjust
= col_iSize
;
854 col_iSize
= col_iSize_before_adjust
= colFrame
->GetMinCoord();
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
));
865 case FLEX_FIXED_SMALL
:
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
,
877 col_iSize
= NSCoordSaturatingAdd(
878 col_iSize
, NSToCoordRound(float(pref_minus_min
) * c
));
881 col_iSize
= col_iSize_before_adjust
= colFrame
->GetMinCoord();
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
;
911 NSCoordSaturatingSubtract(basis
.c
, pref_minus_min
, nscoord_MAX
);
912 col_iSize
= NSCoordSaturatingAdd(
913 col_iSize
, NSToCoordRound(float(pref_minus_min
) * c
));
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
;
926 float c
= float(space
) / float(basis
.c
);
928 NSCoordSaturatingSubtract(basis
.c
, col_iSize
, nscoord_MAX
);
929 col_iSize
= NSCoordSaturatingAdd(
930 col_iSize
, NSToCoordRound(float(col_iSize
) * c
));
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
);
947 case FLEX_FIXED_LARGE
:
949 NS_ASSERTION(col_iSize
== colFrame
->GetPrefCoord(),
950 "wrong inline-size assigned");
952 colFrame
->GetHasSpecifiedCoord() || colFrame
->GetPrefCoord() == 0,
954 if (col_iSize
!= 0) {
955 float c
= float(space
) / float(basis
.c
);
957 NSCoordSaturatingSubtract(basis
.c
, col_iSize
, nscoord_MAX
);
958 col_iSize
= NSCoordSaturatingAdd(
959 col_iSize
, NSToCoordRound(float(col_iSize
) * c
));
964 NS_ASSERTION(pct
!= 0.0f
|| colFrame
->GetPrefCoord() == 0,
967 float c
= float(space
) / basis
.f
;
968 col_iSize
= NSCoordSaturatingAdd(col_iSize
, NSToCoordRound(pct
* c
));
972 case FLEX_ALL_LARGE
: {
973 float c
= float(space
) / float(basis
.c
);
974 col_iSize
= NSCoordSaturatingAdd(col_iSize
, NSToCoordRound(c
));
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?");
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
999 colFrame
->AddSpanCoords(col_iSize
, col_iSize
, aSpanHasSpecifiedISize
);
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
);
1007 case BtlsISizeType::FinalISize
: {
1008 nscoord old_final
= colFrame
->GetFinalISize();
1009 colFrame
->SetFinalISize(col_iSize
);
1011 if (old_final
!= col_iSize
) {
1012 mTableFrame
->DidResizeColumns();
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");