1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 // vim:cindent:ts=4:et:sw=4:
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is Mozilla's table layout code.
18 * The Initial Developer of the Original Code is the Mozilla Foundation.
19 * Portions created by the Initial Developer are Copyright (C) 2006
20 * the Initial Developer. All Rights Reserved.
23 * L. David Baron <dbaron@dbaron.org> (original author)
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
40 * Web-compatible algorithms that determine column and table widths,
41 * used for CSS2's 'table-layout: auto'.
44 #include "BasicTableLayoutStrategy.h"
45 #include "nsTableFrame.h"
46 #include "nsTableCellFrame.h"
47 #include "nsLayoutUtils.h"
48 #include "nsGkAtoms.h"
49 #include "SpanningCellSorter.h"
51 namespace css
= mozilla::css
;
53 #undef DEBUG_TABLE_STRATEGY
55 BasicTableLayoutStrategy::BasicTableLayoutStrategy(nsTableFrame
*aTableFrame
)
56 : nsITableLayoutStrategy(nsITableLayoutStrategy::Auto
)
57 , mTableFrame(aTableFrame
)
59 MarkIntrinsicWidthsDirty();
63 BasicTableLayoutStrategy::~BasicTableLayoutStrategy()
68 BasicTableLayoutStrategy::GetMinWidth(nsIRenderingContext
* aRenderingContext
)
70 DISPLAY_MIN_WIDTH(mTableFrame
, mMinWidth
);
71 if (mMinWidth
== NS_INTRINSIC_WIDTH_UNKNOWN
)
72 ComputeIntrinsicWidths(aRenderingContext
);
77 BasicTableLayoutStrategy::GetPrefWidth(nsIRenderingContext
* aRenderingContext
,
78 PRBool aComputingSize
)
80 DISPLAY_PREF_WIDTH(mTableFrame
, mPrefWidth
);
81 NS_ASSERTION((mPrefWidth
== NS_INTRINSIC_WIDTH_UNKNOWN
) ==
82 (mPrefWidthPctExpand
== NS_INTRINSIC_WIDTH_UNKNOWN
),
83 "dirtyness out of sync");
84 if (mPrefWidth
== NS_INTRINSIC_WIDTH_UNKNOWN
)
85 ComputeIntrinsicWidths(aRenderingContext
);
86 return aComputingSize
? mPrefWidthPctExpand
: mPrefWidth
;
89 struct CellWidthInfo
{
90 CellWidthInfo(nscoord aMinCoord
, nscoord aPrefCoord
,
91 float aPrefPercent
, PRBool aHasSpecifiedWidth
)
92 : hasSpecifiedWidth(aHasSpecifiedWidth
)
94 , prefCoord(aPrefCoord
)
95 , prefPercent(aPrefPercent
)
99 PRBool hasSpecifiedWidth
;
105 // Used for both column and cell calculations. The parts needed only
106 // for cells are skipped when aIsCell is false.
108 GetWidthInfo(nsIRenderingContext
*aRenderingContext
,
109 nsIFrame
*aFrame
, PRBool aIsCell
)
111 nscoord minCoord
, prefCoord
;
113 minCoord
= aFrame
->GetMinWidth(aRenderingContext
);
114 prefCoord
= aFrame
->GetPrefWidth(aRenderingContext
);
119 float prefPercent
= 0.0f
;
120 PRBool hasSpecifiedWidth
= PR_FALSE
;
122 // XXXldb Should we consider -moz-box-sizing?
124 const nsStylePosition
*stylePos
= aFrame
->GetStylePosition();
125 const nsStyleCoord
&width
= stylePos
->mWidth
;
126 nsStyleUnit unit
= width
.GetUnit();
127 // NOTE: We're ignoring calc() units here, for lack of a sensible
128 // idea for what to do with them. This means calc() is basically
129 // handled like 'auto' for table cells and columns.
130 if (unit
== eStyleUnit_Coord
) {
131 hasSpecifiedWidth
= PR_TRUE
;
132 nscoord w
= nsLayoutUtils::ComputeWidthValue(aRenderingContext
,
133 aFrame
, 0, 0, 0, width
);
134 // Quirk: A cell with "nowrap" set and a coord value for the
135 // width which is bigger than the intrinsic minimum width uses
136 // that coord value as the minimum width.
137 // This is kept up-to-date with dynamic changes to nowrap by code in
138 // nsTableCellFrame::AttributeChanged
139 if (aIsCell
&& w
> minCoord
&&
140 aFrame
->PresContext()->CompatibilityMode() ==
141 eCompatibility_NavQuirks
&&
142 aFrame
->GetContent()->HasAttr(kNameSpaceID_None
,
143 nsGkAtoms::nowrap
)) {
146 prefCoord
= NS_MAX(w
, minCoord
);
147 } else if (unit
== eStyleUnit_Percent
) {
148 prefPercent
= width
.GetPercentValue();
149 } else if (unit
== eStyleUnit_Enumerated
&& aIsCell
) {
150 switch (width
.GetIntValue()) {
151 case NS_STYLE_WIDTH_MAX_CONTENT
:
152 // 'width' only affects pref width, not min
153 // width, so don't change anything
155 case NS_STYLE_WIDTH_MIN_CONTENT
:
156 prefCoord
= minCoord
;
158 case NS_STYLE_WIDTH_FIT_CONTENT
:
159 case NS_STYLE_WIDTH_AVAILABLE
:
160 // act just like 'width: auto'
163 NS_NOTREACHED("unexpected enumerated value");
167 nsStyleCoord
maxWidth(stylePos
->mMaxWidth
);
168 if (maxWidth
.GetUnit() == eStyleUnit_Enumerated
) {
169 if (!aIsCell
|| maxWidth
.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE
)
170 maxWidth
.SetNoneValue();
171 else if (maxWidth
.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT
)
172 // for 'max-width', '-moz-fit-content' is like
173 // '-moz-max-content'
174 maxWidth
.SetIntValue(NS_STYLE_WIDTH_MAX_CONTENT
,
175 eStyleUnit_Enumerated
);
177 unit
= maxWidth
.GetUnit();
178 // XXX To really implement 'max-width' well, we'd need to store
179 // it separately on the columns.
180 if (unit
== eStyleUnit_Coord
|| unit
== eStyleUnit_Enumerated
) {
182 nsLayoutUtils::ComputeWidthValue(aRenderingContext
, aFrame
,
188 } else if (unit
== eStyleUnit_Percent
) {
189 float p
= stylePos
->mMaxWidth
.GetPercentValue();
193 // treat calc() on max-width just like 'none'.
195 nsStyleCoord
minWidth(stylePos
->mMinWidth
);
196 if (minWidth
.GetUnit() == eStyleUnit_Enumerated
) {
197 if (!aIsCell
|| minWidth
.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE
)
198 minWidth
.SetCoordValue(0);
199 else if (minWidth
.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT
)
200 // for 'min-width', '-moz-fit-content' is like
201 // '-moz-min-content'
202 minWidth
.SetIntValue(NS_STYLE_WIDTH_MIN_CONTENT
,
203 eStyleUnit_Enumerated
);
205 unit
= minWidth
.GetUnit();
206 if (unit
== eStyleUnit_Coord
|| unit
== eStyleUnit_Enumerated
) {
208 nsLayoutUtils::ComputeWidthValue(aRenderingContext
, aFrame
,
214 } else if (unit
== eStyleUnit_Percent
) {
215 float p
= stylePos
->mMinWidth
.GetPercentValue();
219 // treat calc() on min-width just like '0'.
221 // XXX Should col frame have border/padding considered?
223 nsIFrame::IntrinsicWidthOffsetData offsets
=
224 aFrame
->IntrinsicWidthOffsets(aRenderingContext
);
225 // XXX Should we ignore percentage padding?
226 nscoord add
= offsets
.hPadding
+ offsets
.hBorder
;
228 prefCoord
= NSCoordSaturatingAdd(prefCoord
, add
);
231 return CellWidthInfo(minCoord
, prefCoord
, prefPercent
, hasSpecifiedWidth
);
234 static inline CellWidthInfo
235 GetCellWidthInfo(nsIRenderingContext
*aRenderingContext
,
236 nsTableCellFrame
*aCellFrame
)
238 return GetWidthInfo(aRenderingContext
, aCellFrame
, PR_TRUE
);
241 static inline CellWidthInfo
242 GetColWidthInfo(nsIRenderingContext
*aRenderingContext
,
245 return GetWidthInfo(aRenderingContext
, aFrame
, PR_FALSE
);
250 * The algorithm in this function, in addition to meeting the
251 * requirements of Web-compatibility, is also invariant under reordering
252 * of the rows within a table (something that most, but not all, other
256 BasicTableLayoutStrategy::ComputeColumnIntrinsicWidths(nsIRenderingContext
* aRenderingContext
)
258 nsTableFrame
*tableFrame
= mTableFrame
;
259 nsTableCellMap
*cellMap
= tableFrame
->GetCellMap();
261 SpanningCellSorter
spanningCells(tableFrame
->PresContext()->PresShell());
263 // Loop over the columns to consider the columns and cells *without*
265 PRInt32 col
, col_end
;
266 for (col
= 0, col_end
= cellMap
->GetColCount(); col
< col_end
; ++col
) {
267 nsTableColFrame
*colFrame
= tableFrame
->GetColFrame(col
);
269 NS_ERROR("column frames out of sync with cell map");
272 colFrame
->ResetIntrinsics();
273 colFrame
->ResetSpanIntrinsics();
275 // Consider the widths on the column.
276 CellWidthInfo colInfo
= GetColWidthInfo(aRenderingContext
, colFrame
);
277 colFrame
->AddCoords(colInfo
.minCoord
, colInfo
.prefCoord
,
278 colInfo
.hasSpecifiedWidth
);
279 colFrame
->AddPrefPercent(colInfo
.prefPercent
);
281 // Consider the widths on the column-group. Note that we follow
282 // what the HTML spec says here, and make the width apply to
283 // each column in the group, not the group as a whole.
285 // If column has width, column-group doesn't override width.
286 if (colInfo
.minCoord
== 0 && colInfo
.prefCoord
== 0 &&
287 colInfo
.prefPercent
== 0.0f
) {
288 NS_ASSERTION(colFrame
->GetParent()->GetType() ==
289 nsGkAtoms::tableColGroupFrame
,
290 "expected a column-group");
291 colInfo
= GetColWidthInfo(aRenderingContext
, colFrame
->GetParent());
292 colFrame
->AddCoords(colInfo
.minCoord
, colInfo
.prefCoord
,
293 colInfo
.hasSpecifiedWidth
);
294 colFrame
->AddPrefPercent(colInfo
.prefPercent
);
297 // Consider the contents of and the widths on the cells without
299 nsCellMapColumnIterator
columnIter(cellMap
, col
);
300 PRInt32 row
, colSpan
;
301 nsTableCellFrame
* cellFrame
;
302 while ((cellFrame
= columnIter
.GetNextFrame(&row
, &colSpan
))) {
304 spanningCells
.AddCell(colSpan
, row
, col
);
308 CellWidthInfo info
= GetCellWidthInfo(aRenderingContext
, cellFrame
);
310 colFrame
->AddCoords(info
.minCoord
, info
.prefCoord
,
311 info
.hasSpecifiedWidth
);
312 colFrame
->AddPrefPercent(info
.prefPercent
);
314 #ifdef DEBUG_dbaron_off
315 printf("table %p col %d nonspan: min=%d pref=%d spec=%d pct=%f\n",
316 mTableFrame
, col
, colFrame
->GetMinCoord(),
317 colFrame
->GetPrefCoord(), colFrame
->GetHasSpecifiedCoord(),
318 colFrame
->GetPrefPercent());
321 #ifdef DEBUG_TABLE_STRATEGY
322 printf("ComputeColumnIntrinsicWidths single\n");
323 mTableFrame
->Dump(PR_FALSE
, PR_TRUE
, PR_FALSE
);
326 // Consider the cells with a colspan that we saved in the loop above
327 // into the spanning cell sorter. We consider these cells by seeing
328 // if they require adding to the widths resulting only from cells
329 // with a smaller colspan, and therefore we must process them sorted
330 // in increasing order by colspan. For each colspan group, we
331 // accumulate new values to accumulate in the column frame's Span*
334 // Considering things only relative to the widths resulting from
335 // cells with smaller colspans (rather than incrementally including
336 // the results from spanning cells, or doing spanning and
337 // non-spanning cells in a single pass) means that layout remains
338 // row-order-invariant and (except for percentage widths that add to
339 // more than 100%) column-order invariant.
341 // Starting with smaller colspans makes it more likely that we
342 // satisfy all the constraints given and don't distribute space to
343 // columns where we don't need it.
344 SpanningCellSorter::Item
*item
;
346 while ((item
= spanningCells
.GetNext(&colSpan
))) {
347 NS_ASSERTION(colSpan
> 1,
348 "cell should not have been put in spanning cell sorter");
350 PRInt32 row
= item
->row
;
352 CellData
*cellData
= cellMap
->GetDataAt(row
, col
);
353 NS_ASSERTION(cellData
&& cellData
->IsOrig(),
354 "bogus result from spanning cell sorter");
356 nsTableCellFrame
*cellFrame
= cellData
->GetCellFrame();
357 NS_ASSERTION(cellFrame
, "bogus result from spanning cell sorter");
359 CellWidthInfo info
= GetCellWidthInfo(aRenderingContext
, cellFrame
);
361 if (info
.prefPercent
> 0.0f
) {
362 DistributePctWidthToColumns(info
.prefPercent
,
365 DistributeWidthToColumns(info
.minCoord
, col
, colSpan
,
366 BTLS_MIN_WIDTH
, info
.hasSpecifiedWidth
);
367 DistributeWidthToColumns(info
.prefCoord
, col
, colSpan
,
368 BTLS_PREF_WIDTH
, info
.hasSpecifiedWidth
);
369 } while ((item
= item
->next
));
371 // Combine the results of the span analysis into the main results,
372 // for each increment of colspan.
374 for (col
= 0, col_end
= cellMap
->GetColCount(); col
< col_end
; ++col
) {
375 nsTableColFrame
*colFrame
= tableFrame
->GetColFrame(col
);
377 NS_ERROR("column frames out of sync with cell map");
381 colFrame
->AccumulateSpanIntrinsics();
382 colFrame
->ResetSpanIntrinsics();
384 #ifdef DEBUG_dbaron_off
385 printf("table %p col %d span %d: min=%d pref=%d spec=%d pct=%f\n",
386 mTableFrame
, col
, colSpan
, colFrame
->GetMinCoord(),
387 colFrame
->GetPrefCoord(), colFrame
->GetHasSpecifiedCoord(),
388 colFrame
->GetPrefPercent());
393 // Prevent percentages from adding to more than 100% by (to be
394 // compatible with other browsers) treating any percentages that would
395 // increase the total percentage to more than 100% as the number that
396 // would increase it to only 100% (which is 0% if we've already hit
397 // 100%). This means layout depends on the order of columns.
398 float pct_used
= 0.0f
;
399 for (col
= 0, col_end
= cellMap
->GetColCount(); col
< col_end
; ++col
) {
400 nsTableColFrame
*colFrame
= tableFrame
->GetColFrame(col
);
402 NS_ERROR("column frames out of sync with cell map");
406 colFrame
->AdjustPrefPercent(&pct_used
);
409 #ifdef DEBUG_TABLE_STRATEGY
410 printf("ComputeColumnIntrinsicWidths spanning\n");
411 mTableFrame
->Dump(PR_FALSE
, PR_TRUE
, PR_FALSE
);
416 BasicTableLayoutStrategy::ComputeIntrinsicWidths(nsIRenderingContext
* aRenderingContext
)
418 ComputeColumnIntrinsicWidths(aRenderingContext
);
420 nsTableCellMap
*cellMap
= mTableFrame
->GetCellMap();
421 nscoord min
= 0, pref
= 0, max_small_pct_pref
= 0, nonpct_pref_total
= 0;
422 float pct_total
= 0.0f
; // always from 0.0f - 1.0f
423 PRInt32 colCount
= cellMap
->GetColCount();
424 nscoord spacing
= mTableFrame
->GetCellSpacingX();
425 nscoord add
= spacing
; // add (colcount + 1) * spacing for columns
426 // where a cell originates
428 for (PRInt32 col
= 0; col
< colCount
; ++col
) {
429 nsTableColFrame
*colFrame
= mTableFrame
->GetColFrame(col
);
431 NS_ERROR("column frames out of sync with cell map");
434 if (mTableFrame
->ColumnHasCellSpacingBefore(col
)) {
437 min
+= colFrame
->GetMinCoord();
438 pref
= NSCoordSaturatingAdd(pref
, colFrame
->GetPrefCoord());
440 // Percentages are of the table, so we have to reverse them for
442 float p
= colFrame
->GetPrefPercent();
444 nscoord colPref
= colFrame
->GetPrefCoord();
445 nscoord new_small_pct_expand
=
446 (colPref
== nscoord_MAX
?
447 nscoord_MAX
: nscoord(float(colPref
) / p
));
448 if (new_small_pct_expand
> max_small_pct_pref
) {
449 max_small_pct_pref
= new_small_pct_expand
;
453 nonpct_pref_total
= NSCoordSaturatingAdd(nonpct_pref_total
,
454 colFrame
->GetPrefCoord());
458 nscoord pref_pct_expand
= pref
;
460 // Account for small percentages expanding the preferred width of
462 if (max_small_pct_pref
> pref_pct_expand
) {
463 pref_pct_expand
= max_small_pct_pref
;
466 // Account for large percentages expanding the preferred width of
467 // themselves. There's no need to iterate over the columns multiple
468 // times, since when there is such a need, the small percentage
469 // effect is bigger anyway. (I think!)
470 NS_ASSERTION(0.0f
<= pct_total
&& pct_total
<= 1.0f
,
471 "column percentage widths not adjusted down to 100%");
472 if (pct_total
== 1.0f
) {
473 if (nonpct_pref_total
> 0) {
474 pref_pct_expand
= nscoord_MAX
;
475 // XXX Or should I use some smaller value? (Test this using
479 nscoord large_pct_pref
=
480 (nonpct_pref_total
== nscoord_MAX
?
482 nscoord(float(nonpct_pref_total
) / (1.0f
- pct_total
)));
483 if (large_pct_pref
> pref_pct_expand
)
484 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 mPrefWidthPctExpand
= pref_pct_expand
;
500 BasicTableLayoutStrategy::MarkIntrinsicWidthsDirty()
502 mMinWidth
= NS_INTRINSIC_WIDTH_UNKNOWN
;
503 mPrefWidth
= NS_INTRINSIC_WIDTH_UNKNOWN
;
504 mPrefWidthPctExpand
= NS_INTRINSIC_WIDTH_UNKNOWN
;
505 mLastCalcWidth
= nscoord_MIN
;
509 BasicTableLayoutStrategy::ComputeColumnWidths(const nsHTMLReflowState
& aReflowState
)
511 nscoord width
= aReflowState
.ComputedWidth();
513 if (mLastCalcWidth
== width
)
515 mLastCalcWidth
= width
;
517 NS_ASSERTION((mMinWidth
== NS_INTRINSIC_WIDTH_UNKNOWN
) ==
518 (mPrefWidth
== NS_INTRINSIC_WIDTH_UNKNOWN
),
519 "dirtyness out of sync");
520 NS_ASSERTION((mMinWidth
== NS_INTRINSIC_WIDTH_UNKNOWN
) ==
521 (mPrefWidthPctExpand
== NS_INTRINSIC_WIDTH_UNKNOWN
),
522 "dirtyness out of sync");
523 // XXX Is this needed?
524 if (mMinWidth
== NS_INTRINSIC_WIDTH_UNKNOWN
)
525 ComputeIntrinsicWidths(aReflowState
.rendContext
);
527 nsTableCellMap
*cellMap
= mTableFrame
->GetCellMap();
528 PRInt32 colCount
= cellMap
->GetColCount();
530 return; // nothing to do
532 DistributeWidthToColumns(width
, 0, colCount
, BTLS_FINAL_WIDTH
, PR_FALSE
);
534 #ifdef DEBUG_TABLE_STRATEGY
535 printf("ComputeColumnWidths final\n");
536 mTableFrame
->Dump(PR_FALSE
, PR_TRUE
, PR_FALSE
);
541 BasicTableLayoutStrategy::DistributePctWidthToColumns(float aSpanPrefPct
,
545 // First loop to determine:
546 PRInt32 nonPctColCount
= 0; // number of spanned columns without % width
547 nscoord nonPctTotalPrefWidth
= 0; // total pref width of those columns
548 // and to reduce aSpanPrefPct by columns that already have % width
550 PRInt32 scol
, scol_end
;
551 for (scol
= aFirstCol
, scol_end
= aFirstCol
+ aColCount
;
552 scol
< scol_end
; ++scol
) {
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 nonPctTotalPrefWidth
+= scolFrame
->GetPrefCoord();
563 aSpanPrefPct
-= scolPct
;
567 if (aSpanPrefPct
<= 0.0f
|| nonPctColCount
== 0) {
568 // There's no %-width on the colspan left over to distribute,
569 // or there are no columns to which we could distribute %-width
573 // Second loop, to distribute what remains of aSpanPrefPct
574 // between the non-percent-width spanned columns
575 const PRBool spanHasNonPctPref
= nonPctTotalPrefWidth
> 0; // Loop invariant
576 for (scol
= aFirstCol
, scol_end
= aFirstCol
+ aColCount
;
577 scol
< scol_end
; ++scol
) {
578 nsTableColFrame
*scolFrame
= mTableFrame
->GetColFrame(scol
);
580 NS_ERROR("column frames out of sync with cell map");
584 if (scolFrame
->GetPrefPercent() == 0.0f
) {
585 NS_ASSERTION((!spanHasNonPctPref
||
586 nonPctTotalPrefWidth
!= 0) &&
588 "should not be zero if we haven't allocated "
591 float allocatedPct
; // % width to be given to this column
592 if (spanHasNonPctPref
) {
593 // Group so we're multiplying by 1.0f when we need
594 // to use up aSpanPrefPct.
595 allocatedPct
= aSpanPrefPct
*
596 (float(scolFrame
->GetPrefCoord()) /
597 float(nonPctTotalPrefWidth
));
599 // distribute equally when all pref widths are 0
600 allocatedPct
= aSpanPrefPct
/ float(nonPctColCount
);
602 // Allocate the percent
603 scolFrame
->AddSpanPrefPercent(allocatedPct
);
605 // To avoid accumulating rounding error from division,
606 // subtract this column's values from the totals.
607 aSpanPrefPct
-= allocatedPct
;
608 nonPctTotalPrefWidth
-= scolFrame
->GetPrefCoord();
612 // No more span-percent-width to distribute --> we're done.
613 NS_ASSERTION(spanHasNonPctPref
?
614 nonPctTotalPrefWidth
== 0 :
616 "No more pct width to distribute, but there are "
617 "still cols that need some.");
625 BasicTableLayoutStrategy::DistributeWidthToColumns(nscoord aWidth
,
628 BtlsWidthType aWidthType
,
629 PRBool aSpanHasSpecifiedWidth
)
631 NS_ASSERTION(aWidthType
!= BTLS_FINAL_WIDTH
||
633 aColCount
== mTableFrame
->GetCellMap()->GetColCount()),
634 "Computing final column widths, but didn't get full column range");
636 // border-spacing isn't part of the basis for percentages.
637 nscoord spacing
= mTableFrame
->GetCellSpacingX();
638 nscoord subtract
= 0;
639 // aWidth initially includes border-spacing for the boundaries in between
640 // each of the columns. We start at aFirstCol + 1 because the first
641 // in-between boundary would be at the left edge of column aFirstCol + 1
642 for (PRInt32 col
= aFirstCol
+ 1; col
< aFirstCol
+ aColCount
; ++col
) {
643 if (mTableFrame
->ColumnHasCellSpacingBefore(col
)) {
647 if (aWidthType
== BTLS_FINAL_WIDTH
) {
648 // If we're computing final col-width, then aWidth initially includes
649 // border spacing on the table's far left + far right edge, too. Need
650 // to subtract those out, too.
651 subtract
+= spacing
* 2;
653 aWidth
= NSCoordSaturatingSubtract(aWidth
, subtract
, nscoord_MAX
);
656 * The goal of this function is to distribute |aWidth| between the
657 * columns by making an appropriate AddSpanCoords or SetFinalWidth
658 * call for each column. (We call AddSpanCoords if we're
659 * distributing a column-spanning cell's minimum or preferred width
660 * to its spanned columns. We call SetFinalWidth if we're
661 * distributing a table's final width to its columns.)
663 * The idea is to either assign one of the following sets of widths
664 * or a weighted average of two adjacent sets of widths. It is not
665 * possible to assign values smaller than the smallest set of
666 * widths. However, see below for handling the case of assigning
667 * values larger than the largest set of widths. From smallest to
668 * largest, these are:
670 * 1. [guess_min] Assign all columns their min width.
672 * 2. [guess_min_pct] Assign all columns with percentage widths
673 * their percentage width, and all other columns their min width.
675 * 3. [guess_min_spec] Assign all columns with percentage widths
676 * their percentage width, all columns with specified coordinate
677 * widths their pref width (since it doesn't matter whether it's the
678 * largest contributor to the pref width that was the specified
679 * contributor), and all other columns their min width.
681 * 4. [guess_pref] Assign all columns with percentage widths their
682 * specified width, and all other columns their pref width.
684 * If |aWidth| is *larger* than what we would assign in (4), then we
685 * expand the columns:
687 * a. if any columns without a specified coordinate width or
688 * percent width have nonzero pref width, in proportion to pref
689 * width [total_flex_pref]
691 * b. (NOTE: this case is for BTLS_FINAL_WIDTH only) otherwise, if
692 * any columns without a specified coordinate width or percent
693 * width, but with cells originating in them have zero pref width,
694 * equally between these [numNonSpecZeroWidthCols]
696 * c. otherwise, if any columns without percent width have nonzero
697 * pref width, in proportion to pref width [total_fixed_pref]
699 * d. otherwise, if any columns have nonzero percentage widths, in
700 * proportion to the percentage widths [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,
713 total_fixed_pref
= 0;
714 float total_pct
= 0.0f
; // 0.0f to 1.0f
715 PRInt32 numInfiniteWidthCols
= 0;
716 PRInt32 numNonSpecZeroWidthCols
= 0;
719 nsTableCellMap
*cellMap
= mTableFrame
->GetCellMap();
720 for (col
= aFirstCol
; col
< aFirstCol
+ aColCount
; ++col
) {
721 nsTableColFrame
*colFrame
= mTableFrame
->GetColFrame(col
);
723 NS_ERROR("column frames out of sync with cell map");
726 nscoord min_width
= colFrame
->GetMinCoord();
727 guess_min
+= min_width
;
728 if (colFrame
->GetPrefPercent() != 0.0f
) {
729 float pct
= colFrame
->GetPrefPercent();
731 nscoord val
= nscoord(float(aWidth
) * pct
);
734 guess_min_pct
+= val
;
735 guess_pref
= NSCoordSaturatingAdd(guess_pref
, val
);
737 nscoord pref_width
= colFrame
->GetPrefCoord();
738 if (pref_width
== nscoord_MAX
) {
739 ++numInfiniteWidthCols
;
741 guess_pref
= NSCoordSaturatingAdd(guess_pref
, pref_width
);
742 guess_min_pct
+= min_width
;
743 if (colFrame
->GetHasSpecifiedCoord()) {
744 // we'll add on the rest of guess_min_spec outside the
746 nscoord delta
= NSCoordSaturatingSubtract(pref_width
,
748 guess_min_spec
= NSCoordSaturatingAdd(guess_min_spec
, delta
);
749 total_fixed_pref
= NSCoordSaturatingAdd(total_fixed_pref
,
751 } else if (pref_width
== 0) {
752 if (aWidthType
== BTLS_FINAL_WIDTH
&&
753 cellMap
->GetNumCellsOriginatingInCol(col
) > 0) {
754 ++numNonSpecZeroWidthCols
;
757 total_flex_pref
= NSCoordSaturatingAdd(total_flex_pref
,
762 guess_min_spec
= NSCoordSaturatingAdd(guess_min_spec
, guess_min_pct
);
764 // Determine what we're flexing:
766 FLEX_PCT_SMALL
, // between (1) and (2) above
767 FLEX_FIXED_SMALL
, // between (2) and (3) above
768 FLEX_FLEX_SMALL
, // between (3) and (4) above
769 FLEX_FLEX_LARGE
, // greater than (4) above, case (a)
770 FLEX_FLEX_LARGE_ZERO
, // greater than (4) above, case (b)
771 FLEX_FIXED_LARGE
, // greater than (4) above, case (c)
772 FLEX_PCT_LARGE
, // greater than (4) above, case (d)
773 FLEX_ALL_LARGE
// greater than (4) above, case (e)
777 // These are constants (over columns) for each case's math. We use
778 // a pair of nscoords rather than a float so that we can subtract
779 // each column's allocation so we avoid accumulating rounding error.
780 nscoord space
; // the amount of extra width to allocate
784 } basis
; // the sum of the statistic over columns to divide it
785 if (aWidth
< guess_pref
) {
786 if (aWidthType
!= BTLS_FINAL_WIDTH
&& aWidth
<= guess_min
) {
787 // Return early -- we don't have any extra space to distribute.
790 NS_ASSERTION(!(aWidthType
== BTLS_FINAL_WIDTH
&& aWidth
< guess_min
),
791 "Table width is less than the "
792 "sum of its columns' min widths");
793 if (aWidth
< guess_min_pct
) {
794 l2t
= FLEX_PCT_SMALL
;
795 space
= aWidth
- guess_min
;
796 basis
.c
= guess_min_pct
- guess_min
;
797 } else if (aWidth
< guess_min_spec
) {
798 l2t
= FLEX_FIXED_SMALL
;
799 space
= aWidth
- guess_min_pct
;
800 basis
.c
= NSCoordSaturatingSubtract(guess_min_spec
, guess_min_pct
,
803 l2t
= FLEX_FLEX_SMALL
;
804 space
= aWidth
- guess_min_spec
;
805 basis
.c
= NSCoordSaturatingSubtract(guess_pref
, guess_min_spec
,
809 space
= NSCoordSaturatingSubtract(aWidth
, guess_pref
, nscoord_MAX
);
810 if (total_flex_pref
> 0) {
811 l2t
= FLEX_FLEX_LARGE
;
812 basis
.c
= total_flex_pref
;
813 } else if (numNonSpecZeroWidthCols
> 0) {
814 NS_ASSERTION(aWidthType
== BTLS_FINAL_WIDTH
,
815 "numNonSpecZeroWidthCols should only "
816 "be set when we're setting final width.");
817 l2t
= FLEX_FLEX_LARGE_ZERO
;
818 basis
.c
= numNonSpecZeroWidthCols
;
819 } else if (total_fixed_pref
> 0) {
820 l2t
= FLEX_FIXED_LARGE
;
821 basis
.c
= total_fixed_pref
;
822 } else if (total_pct
> 0.0f
) {
823 l2t
= FLEX_PCT_LARGE
;
826 l2t
= FLEX_ALL_LARGE
;
831 #ifdef DEBUG_dbaron_off
832 printf("ComputeColumnWidths: %d columns in width %d,\n"
833 " guesses=[%d,%d,%d,%d], totals=[%d,%d,%f],\n"
834 " l2t=%d, space=%d, basis.c=%d\n",
836 guess_min
, guess_min_pct
, guess_min_spec
, guess_pref
,
837 total_flex_pref
, total_fixed_pref
, total_pct
,
838 l2t
, space
, basis
.c
);
841 for (col
= aFirstCol
; col
< aFirstCol
+ aColCount
; ++col
) {
842 nsTableColFrame
*colFrame
= mTableFrame
->GetColFrame(col
);
844 NS_ERROR("column frames out of sync with cell map");
849 float pct
= colFrame
->GetPrefPercent();
851 col_width
= nscoord(float(aWidth
) * pct
);
852 nscoord col_min
= colFrame
->GetMinCoord();
853 if (col_width
< col_min
)
856 col_width
= colFrame
->GetPrefCoord();
859 nscoord col_width_before_adjust
= col_width
;
863 col_width
= col_width_before_adjust
= colFrame
->GetMinCoord();
865 nscoord pct_minus_min
=
866 nscoord(float(aWidth
) * pct
) - col_width
;
867 if (pct_minus_min
> 0) {
868 float c
= float(space
) / float(basis
.c
);
869 basis
.c
-= pct_minus_min
;
870 col_width
+= NSToCoordRound(float(pct_minus_min
) * c
);
874 case FLEX_FIXED_SMALL
:
876 NS_ASSERTION(col_width
== colFrame
->GetPrefCoord(),
877 "wrong width assigned");
878 if (colFrame
->GetHasSpecifiedCoord()) {
879 nscoord col_min
= colFrame
->GetMinCoord();
880 nscoord pref_minus_min
= col_width
- col_min
;
881 col_width
= col_width_before_adjust
= col_min
;
882 if (pref_minus_min
!= 0) {
883 float c
= float(space
) / float(basis
.c
);
884 basis
.c
-= pref_minus_min
;
885 col_width
+= NSToCoordRound(
886 float(pref_minus_min
) * c
);
889 col_width
= col_width_before_adjust
=
890 colFrame
->GetMinCoord();
893 case FLEX_FLEX_SMALL
:
895 !colFrame
->GetHasSpecifiedCoord()) {
896 NS_ASSERTION(col_width
== colFrame
->GetPrefCoord(),
897 "wrong width assigned");
898 nscoord col_min
= colFrame
->GetMinCoord();
899 nscoord pref_minus_min
=
900 NSCoordSaturatingSubtract(col_width
, col_min
, 0);
901 col_width
= col_width_before_adjust
= col_min
;
902 if (pref_minus_min
!= 0) {
903 float c
= float(space
) / float(basis
.c
);
904 // If we have infinite-width cols, then the standard
905 // adjustment to col_width using 'c' won't work,
906 // because basis.c and pref_minus_min are both
907 // nscoord_MAX and will cancel each other out in the
908 // col_width adjustment (making us assign all the
909 // space to the first inf-width col). To correct for
910 // this, we'll also divide by numInfiniteWidthCols to
911 // spread the space equally among the inf-width cols.
912 if (numInfiniteWidthCols
) {
913 if (colFrame
->GetPrefCoord() == nscoord_MAX
) {
914 c
= c
/ float(numInfiniteWidthCols
);
915 --numInfiniteWidthCols
;
920 basis
.c
= NSCoordSaturatingSubtract(basis
.c
,
923 col_width
+= NSToCoordRound(
924 float(pref_minus_min
) * c
);
928 case FLEX_FLEX_LARGE
:
930 !colFrame
->GetHasSpecifiedCoord()) {
931 NS_ASSERTION(col_width
== colFrame
->GetPrefCoord(),
932 "wrong width assigned");
933 if (col_width
!= 0) {
934 if (space
== nscoord_MAX
) {
935 basis
.c
-= col_width
;
936 col_width
= nscoord_MAX
;
938 float c
= float(space
) / float(basis
.c
);
939 basis
.c
-= col_width
;
940 col_width
+= NSToCoordRound(float(col_width
) * c
);
945 case FLEX_FLEX_LARGE_ZERO
:
946 NS_ASSERTION(aWidthType
== BTLS_FINAL_WIDTH
,
947 "FLEX_FLEX_LARGE_ZERO only should be hit "
948 "when we're setting final width.");
950 !colFrame
->GetHasSpecifiedCoord() &&
951 cellMap
->GetNumCellsOriginatingInCol(col
) > 0) {
953 NS_ASSERTION(col_width
== 0 &&
954 colFrame
->GetPrefCoord() == 0,
955 "Since we're in FLEX_FLEX_LARGE_ZERO case, "
956 "all auto-width cols should have zero pref "
958 float c
= float(space
) / float(basis
.c
);
959 col_width
+= NSToCoordRound(c
);
963 case FLEX_FIXED_LARGE
:
965 NS_ASSERTION(col_width
== colFrame
->GetPrefCoord(),
966 "wrong width assigned");
967 NS_ASSERTION(colFrame
->GetHasSpecifiedCoord() ||
968 colFrame
->GetPrefCoord() == 0,
970 if (col_width
!= 0) {
971 float c
= float(space
) / float(basis
.c
);
972 basis
.c
-= col_width
;
973 col_width
+= NSToCoordRound(float(col_width
) * c
);
978 NS_ASSERTION(pct
!= 0.0f
|| colFrame
->GetPrefCoord() == 0,
981 float c
= float(space
) / basis
.f
;
982 col_width
+= NSToCoordRound(pct
* c
);
988 float c
= float(space
) / float(basis
.c
);
989 col_width
+= NSToCoordRound(c
);
995 // Only subtract from space if it's a real number.
996 if (space
!= nscoord_MAX
) {
997 NS_ASSERTION(col_width
!= nscoord_MAX
,
998 "How is col_width nscoord_MAX if space isn't?");
999 NS_ASSERTION(col_width_before_adjust
!= nscoord_MAX
,
1000 "How is col_width_before_adjust nscoord_MAX if space isn't?");
1001 space
-= col_width
- col_width_before_adjust
;
1004 NS_ASSERTION(col_width
>= colFrame
->GetMinCoord(),
1005 "assigned width smaller than min");
1007 // Apply the new width
1008 switch (aWidthType
) {
1009 case BTLS_MIN_WIDTH
:
1011 // Note: AddSpanCoords requires both a min and pref width.
1012 // For the pref width, we'll just pass in our computed
1013 // min width, because the real pref width will be at least
1015 colFrame
->AddSpanCoords(col_width
, col_width
,
1016 aSpanHasSpecifiedWidth
);
1019 case BTLS_PREF_WIDTH
:
1021 // Note: AddSpanCoords requires both a min and pref width.
1022 // For the min width, we'll just pass in 0, because
1023 // the real min width will be at least 0
1024 colFrame
->AddSpanCoords(0, col_width
,
1025 aSpanHasSpecifiedWidth
);
1028 case BTLS_FINAL_WIDTH
:
1030 nscoord old_final
= colFrame
->GetFinalWidth();
1031 colFrame
->SetFinalWidth(col_width
);
1033 if (old_final
!= col_width
)
1034 mTableFrame
->DidResizeColumns();
1039 NS_ASSERTION((space
== 0 || space
== nscoord_MAX
) &&
1040 ((l2t
== FLEX_PCT_LARGE
)
1041 ? (-0.001f
< basis
.f
&& basis
.f
< 0.001f
)
1042 : (basis
.c
== 0 || basis
.c
== nscoord_MAX
)),
1043 "didn't subtract all that we added");