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 #undef DEBUG_TABLE_STRATEGY
53 BasicTableLayoutStrategy::BasicTableLayoutStrategy(nsTableFrame
*aTableFrame
)
54 : nsITableLayoutStrategy(nsITableLayoutStrategy::Auto
)
55 , mTableFrame(aTableFrame
)
57 MarkIntrinsicWidthsDirty();
61 BasicTableLayoutStrategy::~BasicTableLayoutStrategy()
66 BasicTableLayoutStrategy::GetMinWidth(nsIRenderingContext
* aRenderingContext
)
68 DISPLAY_MIN_WIDTH(mTableFrame
, mMinWidth
);
69 if (mMinWidth
== NS_INTRINSIC_WIDTH_UNKNOWN
)
70 ComputeIntrinsicWidths(aRenderingContext
);
75 BasicTableLayoutStrategy::GetPrefWidth(nsIRenderingContext
* aRenderingContext
,
76 PRBool aComputingSize
)
78 DISPLAY_PREF_WIDTH(mTableFrame
, mPrefWidth
);
79 NS_ASSERTION((mPrefWidth
== NS_INTRINSIC_WIDTH_UNKNOWN
) ==
80 (mPrefWidthPctExpand
== NS_INTRINSIC_WIDTH_UNKNOWN
),
81 "dirtyness out of sync");
82 if (mPrefWidth
== NS_INTRINSIC_WIDTH_UNKNOWN
)
83 ComputeIntrinsicWidths(aRenderingContext
);
84 return aComputingSize
? mPrefWidthPctExpand
: mPrefWidth
;
87 struct CellWidthInfo
{
88 CellWidthInfo(nscoord aMinCoord
, nscoord aPrefCoord
,
89 float aPrefPercent
, PRBool aHasSpecifiedWidth
)
90 : hasSpecifiedWidth(aHasSpecifiedWidth
)
92 , prefCoord(aPrefCoord
)
93 , prefPercent(aPrefPercent
)
97 PRBool hasSpecifiedWidth
;
103 // Used for both column and cell calculations. The parts needed only
104 // for cells are skipped when aIsCell is false.
106 GetWidthInfo(nsIRenderingContext
*aRenderingContext
,
107 nsIFrame
*aFrame
, PRBool aIsCell
)
109 nscoord minCoord
, prefCoord
;
111 minCoord
= aFrame
->GetMinWidth(aRenderingContext
);
112 prefCoord
= aFrame
->GetPrefWidth(aRenderingContext
);
117 float prefPercent
= 0.0f
;
118 PRBool hasSpecifiedWidth
= PR_FALSE
;
120 // XXXldb Should we consider -moz-box-sizing?
122 const nsStylePosition
*stylePos
= aFrame
->GetStylePosition();
123 nsStyleUnit unit
= stylePos
->mWidth
.GetUnit();
124 if (unit
== eStyleUnit_Coord
) {
125 hasSpecifiedWidth
= PR_TRUE
;
126 nscoord w
= nsLayoutUtils::ComputeWidthValue(aRenderingContext
,
127 aFrame
, 0, 0, 0, stylePos
->mWidth
);
128 // Quirk: A cell with "nowrap" set and a coord value for the
129 // width which is bigger than the intrinsic minimum width uses
130 // that coord value as the minimum width.
131 // This is kept up-to-date with dynamic changes to nowrap by code in
132 // nsTableCellFrame::AttributeChanged
133 if (aIsCell
&& w
> minCoord
&&
134 aFrame
->PresContext()->CompatibilityMode() ==
135 eCompatibility_NavQuirks
&&
136 aFrame
->GetContent()->HasAttr(kNameSpaceID_None
,
137 nsGkAtoms::nowrap
)) {
140 prefCoord
= PR_MAX(w
, minCoord
);
141 } else if (unit
== eStyleUnit_Percent
) {
142 prefPercent
= stylePos
->mWidth
.GetPercentValue();
143 } else if (unit
== eStyleUnit_Enumerated
&& aIsCell
) {
144 switch (stylePos
->mWidth
.GetIntValue()) {
145 case NS_STYLE_WIDTH_MAX_CONTENT
:
146 // 'width' only affects pref width, not min
147 // width, so don't change anything
149 case NS_STYLE_WIDTH_MIN_CONTENT
:
150 prefCoord
= minCoord
;
152 case NS_STYLE_WIDTH_FIT_CONTENT
:
153 case NS_STYLE_WIDTH_AVAILABLE
:
154 // act just like 'width: auto'
157 NS_NOTREACHED("unexpected enumerated value");
161 nsStyleCoord
maxWidth(stylePos
->mMaxWidth
);
162 if (maxWidth
.GetUnit() == eStyleUnit_Enumerated
) {
163 if (!aIsCell
|| maxWidth
.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE
)
164 maxWidth
.SetNoneValue();
165 else if (maxWidth
.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT
)
166 // for 'max-width', '-moz-fit-content' is like
167 // '-moz-max-content'
168 maxWidth
.SetIntValue(NS_STYLE_WIDTH_MAX_CONTENT
,
169 eStyleUnit_Enumerated
);
171 unit
= maxWidth
.GetUnit();
172 // XXX To really implement 'max-width' well, we'd need to store
173 // it separately on the columns.
174 if (unit
== eStyleUnit_Coord
|| unit
== eStyleUnit_Enumerated
) {
176 nsLayoutUtils::ComputeWidthValue(aRenderingContext
, aFrame
,
182 } else if (unit
== eStyleUnit_Percent
) {
183 float p
= stylePos
->mMaxWidth
.GetPercentValue();
188 nsStyleCoord
minWidth(stylePos
->mMinWidth
);
189 if (minWidth
.GetUnit() == eStyleUnit_Enumerated
) {
190 if (!aIsCell
|| minWidth
.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE
)
191 minWidth
.SetCoordValue(0);
192 else if (minWidth
.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT
)
193 // for 'min-width', '-moz-fit-content' is like
194 // '-moz-min-content'
195 minWidth
.SetIntValue(NS_STYLE_WIDTH_MIN_CONTENT
,
196 eStyleUnit_Enumerated
);
198 unit
= minWidth
.GetUnit();
199 if (unit
== eStyleUnit_Coord
|| unit
== eStyleUnit_Enumerated
) {
201 nsLayoutUtils::ComputeWidthValue(aRenderingContext
, aFrame
,
207 } else if (unit
== eStyleUnit_Percent
) {
208 float p
= stylePos
->mMinWidth
.GetPercentValue();
213 // XXX Should col frame have border/padding considered?
215 nsIFrame::IntrinsicWidthOffsetData offsets
=
216 aFrame
->IntrinsicWidthOffsets(aRenderingContext
);
217 // XXX Should we ignore percentage padding?
218 nscoord add
= offsets
.hPadding
+ offsets
.hBorder
;
220 prefCoord
= NSCoordSaturatingAdd(prefCoord
, add
);
223 return CellWidthInfo(minCoord
, prefCoord
, prefPercent
, hasSpecifiedWidth
);
226 static inline CellWidthInfo
227 GetCellWidthInfo(nsIRenderingContext
*aRenderingContext
,
228 nsTableCellFrame
*aCellFrame
)
230 return GetWidthInfo(aRenderingContext
, aCellFrame
, PR_TRUE
);
233 static inline CellWidthInfo
234 GetColWidthInfo(nsIRenderingContext
*aRenderingContext
,
237 return GetWidthInfo(aRenderingContext
, aFrame
, PR_FALSE
);
242 * The algorithm in this function, in addition to meeting the
243 * requirements of Web-compatibility, is also invariant under reordering
244 * of the rows within a table (something that most, but not all, other
248 BasicTableLayoutStrategy::ComputeColumnIntrinsicWidths(nsIRenderingContext
* aRenderingContext
)
250 nsTableFrame
*tableFrame
= mTableFrame
;
251 nsTableCellMap
*cellMap
= tableFrame
->GetCellMap();
253 SpanningCellSorter
spanningCells(tableFrame
->PresContext()->PresShell());
255 // Loop over the columns to consider the columns and cells *without*
257 PRInt32 col
, col_end
;
258 for (col
= 0, col_end
= cellMap
->GetColCount(); col
< col_end
; ++col
) {
259 nsTableColFrame
*colFrame
= tableFrame
->GetColFrame(col
);
261 NS_ERROR("column frames out of sync with cell map");
264 colFrame
->ResetIntrinsics();
265 colFrame
->ResetSpanIntrinsics();
267 // Consider the widths on the column.
268 CellWidthInfo colInfo
= GetColWidthInfo(aRenderingContext
, colFrame
);
269 colFrame
->AddCoords(colInfo
.minCoord
, colInfo
.prefCoord
,
270 colInfo
.hasSpecifiedWidth
);
271 colFrame
->AddPrefPercent(colInfo
.prefPercent
);
273 // Consider the widths on the column-group. Note that we follow
274 // what the HTML spec says here, and make the width apply to
275 // each column in the group, not the group as a whole.
276 // XXX Should we be doing this when we have widths on the column?
277 NS_ASSERTION(colFrame
->GetParent()->GetType() ==
278 nsGkAtoms::tableColGroupFrame
,
279 "expected a column-group");
280 colInfo
= GetColWidthInfo(aRenderingContext
, colFrame
->GetParent());
281 colFrame
->AddCoords(colInfo
.minCoord
, colInfo
.prefCoord
,
282 colInfo
.hasSpecifiedWidth
);
283 colFrame
->AddPrefPercent(colInfo
.prefPercent
);
285 // Consider the contents of and the widths on the cells without
287 nsCellMapColumnIterator
columnIter(cellMap
, col
);
288 PRInt32 row
, colSpan
;
289 nsTableCellFrame
* cellFrame
;
290 while ((cellFrame
= columnIter
.GetNextFrame(&row
, &colSpan
))) {
292 spanningCells
.AddCell(colSpan
, row
, col
);
296 CellWidthInfo info
= GetCellWidthInfo(aRenderingContext
, cellFrame
);
298 colFrame
->AddCoords(info
.minCoord
, info
.prefCoord
,
299 info
.hasSpecifiedWidth
);
300 colFrame
->AddPrefPercent(info
.prefPercent
);
302 #ifdef DEBUG_dbaron_off
303 printf("table %p col %d nonspan: min=%d pref=%d spec=%d pct=%f\n",
304 mTableFrame
, col
, colFrame
->GetMinCoord(),
305 colFrame
->GetPrefCoord(), colFrame
->GetHasSpecifiedCoord(),
306 colFrame
->GetPrefPercent());
309 #ifdef DEBUG_TABLE_STRATEGY
310 printf("ComputeColumnIntrinsicWidths single\n");
311 mTableFrame
->Dump(PR_FALSE
, PR_TRUE
, PR_FALSE
);
314 // Consider the cells with a colspan that we saved in the loop above
315 // into the spanning cell sorter. We consider these cells by seeing
316 // if they require adding to the widths resulting only from cells
317 // with a smaller colspan, and therefore we must process them sorted
318 // in increasing order by colspan. For each colspan group, we
319 // accumulate new values to accumulate in the column frame's Span*
322 // Considering things only relative to the widths resulting from
323 // cells with smaller colspans (rather than incrementally including
324 // the results from spanning cells, or doing spanning and
325 // non-spanning cells in a single pass) means that layout remains
326 // row-order-invariant and (except for percentage widths that add to
327 // more than 100%) column-order invariant.
329 // Starting with smaller colspans makes it more likely that we
330 // satisfy all the constraints given and don't distribute space to
331 // columns where we don't need it.
332 SpanningCellSorter::Item
*item
;
334 while ((item
= spanningCells
.GetNext(&colSpan
))) {
335 NS_ASSERTION(colSpan
> 1,
336 "cell should not have been put in spanning cell sorter");
338 PRInt32 row
= item
->row
;
340 CellData
*cellData
= cellMap
->GetDataAt(row
, col
);
341 NS_ASSERTION(cellData
&& cellData
->IsOrig(),
342 "bogus result from spanning cell sorter");
344 nsTableCellFrame
*cellFrame
= cellData
->GetCellFrame();
345 NS_ASSERTION(cellFrame
, "bogus result from spanning cell sorter");
347 CellWidthInfo info
= GetCellWidthInfo(aRenderingContext
, cellFrame
);
349 if (info
.prefPercent
> 0.0f
) {
350 DistributePctWidthToColumns(info
.prefPercent
,
353 DistributeWidthToColumns(info
.minCoord
, col
, colSpan
,
354 BTLS_MIN_WIDTH
, info
.hasSpecifiedWidth
);
355 DistributeWidthToColumns(info
.prefCoord
, col
, colSpan
,
356 BTLS_PREF_WIDTH
, info
.hasSpecifiedWidth
);
357 } while ((item
= item
->next
));
359 // Combine the results of the span analysis into the main results,
360 // for each increment of colspan.
362 for (col
= 0, col_end
= cellMap
->GetColCount(); col
< col_end
; ++col
) {
363 nsTableColFrame
*colFrame
= tableFrame
->GetColFrame(col
);
365 NS_ERROR("column frames out of sync with cell map");
369 colFrame
->AccumulateSpanIntrinsics();
370 colFrame
->ResetSpanIntrinsics();
372 #ifdef DEBUG_dbaron_off
373 printf("table %p col %d span %d: min=%d pref=%d spec=%d pct=%f\n",
374 mTableFrame
, col
, colSpan
, colFrame
->GetMinCoord(),
375 colFrame
->GetPrefCoord(), colFrame
->GetHasSpecifiedCoord(),
376 colFrame
->GetPrefPercent());
381 // Prevent percentages from adding to more than 100% by (to be
382 // compatible with other browsers) treating any percentages that would
383 // increase the total percentage to more than 100% as the number that
384 // would increase it to only 100% (which is 0% if we've already hit
385 // 100%). This means layout depends on the order of columns.
386 float pct_used
= 0.0f
;
387 for (col
= 0, col_end
= cellMap
->GetColCount(); col
< col_end
; ++col
) {
388 nsTableColFrame
*colFrame
= tableFrame
->GetColFrame(col
);
390 NS_ERROR("column frames out of sync with cell map");
394 colFrame
->AdjustPrefPercent(&pct_used
);
397 #ifdef DEBUG_TABLE_STRATEGY
398 printf("ComputeColumnIntrinsicWidths spanning\n");
399 mTableFrame
->Dump(PR_FALSE
, PR_TRUE
, PR_FALSE
);
404 BasicTableLayoutStrategy::ComputeIntrinsicWidths(nsIRenderingContext
* aRenderingContext
)
406 ComputeColumnIntrinsicWidths(aRenderingContext
);
408 nsTableCellMap
*cellMap
= mTableFrame
->GetCellMap();
409 nscoord min
= 0, pref
= 0, max_small_pct_pref
= 0, nonpct_pref_total
= 0;
410 float pct_total
= 0.0f
; // always from 0.0f - 1.0f
411 PRInt32 colCount
= cellMap
->GetColCount();
412 nscoord spacing
= mTableFrame
->GetCellSpacingX();
413 nscoord add
= spacing
; // add (colcount + 1) * spacing for columns
414 // where a cell originates
416 for (PRInt32 col
= 0; col
< colCount
; ++col
) {
417 nsTableColFrame
*colFrame
= mTableFrame
->GetColFrame(col
);
419 NS_ERROR("column frames out of sync with cell map");
422 if (mTableFrame
->ColumnHasCellSpacingBefore(col
)) {
425 min
+= colFrame
->GetMinCoord();
426 pref
= NSCoordSaturatingAdd(pref
, colFrame
->GetPrefCoord());
428 // Percentages are of the table, so we have to reverse them for
430 float p
= colFrame
->GetPrefPercent();
432 nscoord colPref
= colFrame
->GetPrefCoord();
433 nscoord new_small_pct_expand
=
434 (colPref
== nscoord_MAX
?
435 nscoord_MAX
: nscoord(float(colPref
) / p
));
436 if (new_small_pct_expand
> max_small_pct_pref
) {
437 max_small_pct_pref
= new_small_pct_expand
;
441 nonpct_pref_total
= NSCoordSaturatingAdd(nonpct_pref_total
,
442 colFrame
->GetPrefCoord());
446 nscoord pref_pct_expand
= pref
;
448 // Account for small percentages expanding the preferred width of
450 if (max_small_pct_pref
> pref_pct_expand
) {
451 pref_pct_expand
= max_small_pct_pref
;
454 // Account for large percentages expanding the preferred width of
455 // themselves. There's no need to iterate over the columns multiple
456 // times, since when there is such a need, the small percentage
457 // effect is bigger anyway. (I think!)
458 NS_ASSERTION(0.0f
<= pct_total
&& pct_total
<= 1.0f
,
459 "column percentage widths not adjusted down to 100%");
460 if (pct_total
== 1.0f
) {
461 if (nonpct_pref_total
> 0) {
462 pref_pct_expand
= nscoord_MAX
;
463 // XXX Or should I use some smaller value? (Test this using
467 nscoord large_pct_pref
=
468 (nonpct_pref_total
== nscoord_MAX
?
470 nscoord(float(nonpct_pref_total
) / (1.0f
- pct_total
)));
471 if (large_pct_pref
> pref_pct_expand
)
472 pref_pct_expand
= large_pct_pref
;
475 // border-spacing isn't part of the basis for percentages
478 pref
= NSCoordSaturatingAdd(pref
, add
);
479 pref_pct_expand
= NSCoordSaturatingAdd(pref_pct_expand
, add
);
484 mPrefWidthPctExpand
= pref_pct_expand
;
488 BasicTableLayoutStrategy::MarkIntrinsicWidthsDirty()
490 mMinWidth
= NS_INTRINSIC_WIDTH_UNKNOWN
;
491 mPrefWidth
= NS_INTRINSIC_WIDTH_UNKNOWN
;
492 mPrefWidthPctExpand
= NS_INTRINSIC_WIDTH_UNKNOWN
;
493 mLastCalcWidth
= nscoord_MIN
;
497 BasicTableLayoutStrategy::ComputeColumnWidths(const nsHTMLReflowState
& aReflowState
)
499 nscoord width
= aReflowState
.ComputedWidth();
501 if (mLastCalcWidth
== width
)
503 mLastCalcWidth
= width
;
505 NS_ASSERTION((mMinWidth
== NS_INTRINSIC_WIDTH_UNKNOWN
) ==
506 (mPrefWidth
== NS_INTRINSIC_WIDTH_UNKNOWN
),
507 "dirtyness out of sync");
508 NS_ASSERTION((mMinWidth
== NS_INTRINSIC_WIDTH_UNKNOWN
) ==
509 (mPrefWidthPctExpand
== NS_INTRINSIC_WIDTH_UNKNOWN
),
510 "dirtyness out of sync");
511 // XXX Is this needed?
512 if (mMinWidth
== NS_INTRINSIC_WIDTH_UNKNOWN
)
513 ComputeIntrinsicWidths(aReflowState
.rendContext
);
515 nsTableCellMap
*cellMap
= mTableFrame
->GetCellMap();
516 PRInt32 colCount
= cellMap
->GetColCount();
518 return; // nothing to do
520 DistributeWidthToColumns(width
, 0, colCount
, BTLS_FINAL_WIDTH
, PR_FALSE
);
522 #ifdef DEBUG_TABLE_STRATEGY
523 printf("ComputeColumnWidths final\n");
524 mTableFrame
->Dump(PR_FALSE
, PR_TRUE
, PR_FALSE
);
529 BasicTableLayoutStrategy::DistributePctWidthToColumns(float aSpanPrefPct
,
533 // First loop to determine:
534 PRInt32 nonPctColCount
= 0; // number of spanned columns without % width
535 nscoord nonPctTotalPrefWidth
= 0; // total pref width of those columns
536 // and to reduce aSpanPrefPct by columns that already have % width
538 PRInt32 scol
, scol_end
;
539 for (scol
= aFirstCol
, scol_end
= aFirstCol
+ aColCount
;
540 scol
< scol_end
; ++scol
) {
541 nsTableColFrame
*scolFrame
= mTableFrame
->GetColFrame(scol
);
543 NS_ERROR("column frames out of sync with cell map");
546 float scolPct
= scolFrame
->GetPrefPercent();
547 if (scolPct
== 0.0f
) {
548 nonPctTotalPrefWidth
+= scolFrame
->GetPrefCoord();
551 aSpanPrefPct
-= scolPct
;
555 if (aSpanPrefPct
<= 0.0f
|| nonPctColCount
== 0) {
556 // There's no %-width on the colspan left over to distribute,
557 // or there are no columns to which we could distribute %-width
561 // Second loop, to distribute what remains of aSpanPrefPct
562 // between the non-percent-width spanned columns
563 const PRBool spanHasNonPctPref
= nonPctTotalPrefWidth
> 0; // Loop invariant
564 for (scol
= aFirstCol
, scol_end
= aFirstCol
+ aColCount
;
565 scol
< scol_end
; ++scol
) {
566 nsTableColFrame
*scolFrame
= mTableFrame
->GetColFrame(scol
);
568 NS_ERROR("column frames out of sync with cell map");
572 if (scolFrame
->GetPrefPercent() == 0.0f
) {
573 NS_ASSERTION((!spanHasNonPctPref
||
574 nonPctTotalPrefWidth
!= 0) &&
576 "should not be zero if we haven't allocated "
579 float allocatedPct
; // % width to be given to this column
580 if (spanHasNonPctPref
) {
581 // Group so we're multiplying by 1.0f when we need
582 // to use up aSpanPrefPct.
583 allocatedPct
= aSpanPrefPct
*
584 (float(scolFrame
->GetPrefCoord()) /
585 float(nonPctTotalPrefWidth
));
587 // distribute equally when all pref widths are 0
588 allocatedPct
= aSpanPrefPct
/ float(nonPctColCount
);
590 // Allocate the percent
591 scolFrame
->AddSpanPrefPercent(allocatedPct
);
593 // To avoid accumulating rounding error from division,
594 // subtract this column's values from the totals.
595 aSpanPrefPct
-= allocatedPct
;
596 nonPctTotalPrefWidth
-= scolFrame
->GetPrefCoord();
600 // No more span-percent-width to distribute --> we're done.
601 NS_ASSERTION(spanHasNonPctPref
?
602 nonPctTotalPrefWidth
== 0 :
604 "No more pct width to distribute, but there are "
605 "still cols that need some.");
613 BasicTableLayoutStrategy::DistributeWidthToColumns(nscoord aWidth
,
616 BtlsWidthType aWidthType
,
617 PRBool aSpanHasSpecifiedWidth
)
619 NS_ASSERTION(aWidthType
!= BTLS_FINAL_WIDTH
||
621 aColCount
== mTableFrame
->GetCellMap()->GetColCount()),
622 "Computing final column widths, but didn't get full column range");
624 // border-spacing isn't part of the basis for percentages.
625 nscoord spacing
= mTableFrame
->GetCellSpacingX();
626 nscoord subtract
= 0;
627 // aWidth initially includes border-spacing for the boundaries in between
628 // each of the columns. We start at aFirstCol + 1 because the first
629 // in-between boundary would be at the left edge of column aFirstCol + 1
630 for (PRInt32 col
= aFirstCol
+ 1; col
< aFirstCol
+ aColCount
; ++col
) {
631 if (mTableFrame
->ColumnHasCellSpacingBefore(col
)) {
635 if (aWidthType
== BTLS_FINAL_WIDTH
) {
636 // If we're computing final col-width, then aWidth initially includes
637 // border spacing on the table's far left + far right edge, too. Need
638 // to subtract those out, too.
639 subtract
+= spacing
* 2;
641 aWidth
= NSCoordSaturatingSubtract(aWidth
, subtract
, nscoord_MAX
);
644 * The goal of this function is to distribute |aWidth| between the
645 * columns by making an appropriate AddSpanCoords or SetFinalWidth
646 * call for each column. (We call AddSpanCoords if we're
647 * distributing a column-spanning cell's minimum or preferred width
648 * to its spanned columns. We call SetFinalWidth if we're
649 * distributing a table's final width to its columns.)
651 * The idea is to either assign one of the following sets of widths
652 * or a weighted average of two adjacent sets of widths. It is not
653 * possible to assign values smaller than the smallest set of
654 * widths. However, see below for handling the case of assigning
655 * values larger than the largest set of widths. From smallest to
656 * largest, these are:
658 * 1. [guess_min] Assign all columns their min width.
660 * 2. [guess_min_pct] Assign all columns with percentage widths
661 * their percentage width, and all other columns their min width.
663 * 3. [guess_min_spec] Assign all columns with percentage widths
664 * their percentage width, all columns with specified coordinate
665 * widths their pref width (since it doesn't matter whether it's the
666 * largest contributor to the pref width that was the specified
667 * contributor), and all other columns their min width.
669 * 4. [guess_pref] Assign all columns with percentage widths their
670 * specified width, and all other columns their pref width.
672 * If |aWidth| is *larger* than what we would assign in (4), then we
673 * expand the columns:
675 * a. if any columns without a specified coordinate width or
676 * percent width have nonzero pref width, in proportion to pref
677 * width [total_flex_pref]
679 * b. (NOTE: this case is for BTLS_FINAL_WIDTH only) otherwise, if
680 * any columns without a specified coordinate width or percent
681 * width, but with cells originating in them have zero pref width,
682 * equally between these [numNonSpecZeroWidthCols]
684 * c. otherwise, if any columns without percent width have nonzero
685 * pref width, in proportion to pref width [total_fixed_pref]
687 * d. otherwise, if any columns have nonzero percentage widths, in
688 * proportion to the percentage widths [total_pct]
690 * e. otherwise, equally.
693 // Loop #1 over the columns, to figure out the four values above so
694 // we know which case we're dealing with.
696 nscoord guess_min
= 0,
701 total_fixed_pref
= 0;
702 float total_pct
= 0.0f
; // 0.0f to 1.0f
703 PRInt32 numInfiniteWidthCols
= 0;
704 PRInt32 numNonSpecZeroWidthCols
= 0;
707 for (col
= aFirstCol
; col
< aFirstCol
+ aColCount
; ++col
) {
708 nsTableColFrame
*colFrame
= mTableFrame
->GetColFrame(col
);
710 NS_ERROR("column frames out of sync with cell map");
713 nscoord min_width
= colFrame
->GetMinCoord();
714 guess_min
+= min_width
;
715 if (colFrame
->GetPrefPercent() != 0.0f
) {
716 float pct
= colFrame
->GetPrefPercent();
718 nscoord val
= nscoord(float(aWidth
) * pct
);
721 guess_min_pct
+= val
;
722 guess_pref
= NSCoordSaturatingAdd(guess_pref
, val
);
724 nscoord pref_width
= colFrame
->GetPrefCoord();
725 if (pref_width
== nscoord_MAX
) {
726 ++numInfiniteWidthCols
;
728 guess_pref
= NSCoordSaturatingAdd(guess_pref
, pref_width
);
729 guess_min_pct
+= min_width
;
730 if (colFrame
->GetHasSpecifiedCoord()) {
731 // we'll add on the rest of guess_min_spec outside the
733 nscoord delta
= NSCoordSaturatingSubtract(pref_width
,
735 guess_min_spec
= NSCoordSaturatingAdd(guess_min_spec
, delta
);
736 total_fixed_pref
= NSCoordSaturatingAdd(total_fixed_pref
,
738 } else if (pref_width
== 0) {
739 if (aWidthType
== BTLS_FINAL_WIDTH
&&
740 mTableFrame
->ColumnHasCellSpacingBefore(col
)) {
741 ++numNonSpecZeroWidthCols
;
744 total_flex_pref
= NSCoordSaturatingAdd(total_flex_pref
,
749 guess_min_spec
= NSCoordSaturatingAdd(guess_min_spec
, guess_min_pct
);
751 // Determine what we're flexing:
753 FLEX_PCT_SMALL
, // between (1) and (2) above
754 FLEX_FIXED_SMALL
, // between (2) and (3) above
755 FLEX_FLEX_SMALL
, // between (3) and (4) above
756 FLEX_FLEX_LARGE
, // greater than (4) above, case (a)
757 FLEX_FLEX_LARGE_ZERO
, // greater than (4) above, case (b)
758 FLEX_FIXED_LARGE
, // greater than (4) above, case (c)
759 FLEX_PCT_LARGE
, // greater than (4) above, case (d)
760 FLEX_ALL_LARGE
// greater than (4) above, case (e)
764 // These are constants (over columns) for each case's math. We use
765 // a pair of nscoords rather than a float so that we can subtract
766 // each column's allocation so we avoid accumulating rounding error.
767 nscoord space
; // the amount of extra width to allocate
771 } basis
; // the sum of the statistic over columns to divide it
772 if (aWidth
< guess_pref
) {
773 if (aWidthType
!= BTLS_FINAL_WIDTH
&& aWidth
<= guess_min
) {
774 // Return early -- we don't have any extra space to distribute.
777 NS_ASSERTION(!(aWidthType
== BTLS_FINAL_WIDTH
&& aWidth
< guess_min
),
778 "Table width is less than the "
779 "sum of its columns' min widths");
780 if (aWidth
< guess_min_pct
) {
781 l2t
= FLEX_PCT_SMALL
;
782 space
= aWidth
- guess_min
;
783 basis
.c
= guess_min_pct
- guess_min
;
784 } else if (aWidth
< guess_min_spec
) {
785 l2t
= FLEX_FIXED_SMALL
;
786 space
= aWidth
- guess_min_pct
;
787 basis
.c
= NSCoordSaturatingSubtract(guess_min_spec
, guess_min_pct
,
790 l2t
= FLEX_FLEX_SMALL
;
791 space
= aWidth
- guess_min_spec
;
792 basis
.c
= NSCoordSaturatingSubtract(guess_pref
, guess_min_spec
,
796 space
= NSCoordSaturatingSubtract(aWidth
, guess_pref
, nscoord_MAX
);
797 if (total_flex_pref
> 0) {
798 l2t
= FLEX_FLEX_LARGE
;
799 basis
.c
= total_flex_pref
;
800 } else if (numNonSpecZeroWidthCols
> 0) {
801 NS_ASSERTION(aWidthType
== BTLS_FINAL_WIDTH
,
802 "numNonSpecZeroWidthCols should only "
803 "be set when we're setting final width.");
804 l2t
= FLEX_FLEX_LARGE_ZERO
;
805 basis
.c
= numNonSpecZeroWidthCols
;
806 } else if (total_fixed_pref
> 0) {
807 l2t
= FLEX_FIXED_LARGE
;
808 basis
.c
= total_fixed_pref
;
809 } else if (total_pct
> 0.0f
) {
810 l2t
= FLEX_PCT_LARGE
;
813 l2t
= FLEX_ALL_LARGE
;
818 #ifdef DEBUG_dbaron_off
819 printf("ComputeColumnWidths: %d columns in width %d,\n"
820 " guesses=[%d,%d,%d,%d], totals=[%d,%d,%f],\n"
821 " l2t=%d, space=%d, basis.c=%d\n",
823 guess_min
, guess_min_pct
, guess_min_spec
, guess_pref
,
824 total_flex_pref
, total_fixed_pref
, total_pct
,
825 l2t
, space
, basis
.c
);
828 for (col
= aFirstCol
; col
< aFirstCol
+ aColCount
; ++col
) {
829 nsTableColFrame
*colFrame
= mTableFrame
->GetColFrame(col
);
831 NS_ERROR("column frames out of sync with cell map");
836 float pct
= colFrame
->GetPrefPercent();
838 col_width
= nscoord(float(aWidth
) * pct
);
839 nscoord col_min
= colFrame
->GetMinCoord();
840 if (col_width
< col_min
)
843 col_width
= colFrame
->GetPrefCoord();
846 nscoord col_width_before_adjust
= col_width
;
850 col_width
= col_width_before_adjust
= colFrame
->GetMinCoord();
852 nscoord pct_minus_min
=
853 nscoord(float(aWidth
) * pct
) - col_width
;
854 if (pct_minus_min
> 0) {
855 float c
= float(space
) / float(basis
.c
);
856 basis
.c
-= pct_minus_min
;
857 col_width
+= NSToCoordRound(float(pct_minus_min
) * c
);
861 case FLEX_FIXED_SMALL
:
863 NS_ASSERTION(col_width
== colFrame
->GetPrefCoord(),
864 "wrong width assigned");
865 if (colFrame
->GetHasSpecifiedCoord()) {
866 nscoord col_min
= colFrame
->GetMinCoord();
867 nscoord pref_minus_min
= col_width
- col_min
;
868 col_width
= col_width_before_adjust
= col_min
;
869 if (pref_minus_min
!= 0) {
870 float c
= float(space
) / float(basis
.c
);
871 basis
.c
-= pref_minus_min
;
872 col_width
+= NSToCoordRound(
873 float(pref_minus_min
) * c
);
876 col_width
= col_width_before_adjust
=
877 colFrame
->GetMinCoord();
880 case FLEX_FLEX_SMALL
:
882 !colFrame
->GetHasSpecifiedCoord()) {
883 NS_ASSERTION(col_width
== colFrame
->GetPrefCoord(),
884 "wrong width assigned");
885 nscoord col_min
= colFrame
->GetMinCoord();
886 nscoord pref_minus_min
=
887 NSCoordSaturatingSubtract(col_width
, col_min
, 0);
888 col_width
= col_width_before_adjust
= col_min
;
889 if (pref_minus_min
!= 0) {
890 float c
= float(space
) / float(basis
.c
);
891 // If we have infinite-width cols, then the standard
892 // adjustment to col_width using 'c' won't work,
893 // because basis.c and pref_minus_min are both
894 // nscoord_MAX and will cancel each other out in the
895 // col_width adjustment (making us assign all the
896 // space to the first inf-width col). To correct for
897 // this, we'll also divide by numInfiniteWidthCols to
898 // spread the space equally among the inf-width cols.
899 if (numInfiniteWidthCols
) {
900 if (colFrame
->GetPrefCoord() == nscoord_MAX
) {
901 c
= c
/ float(numInfiniteWidthCols
);
902 --numInfiniteWidthCols
;
907 basis
.c
= NSCoordSaturatingSubtract(basis
.c
,
910 col_width
+= NSToCoordRound(
911 float(pref_minus_min
) * c
);
915 case FLEX_FLEX_LARGE
:
917 !colFrame
->GetHasSpecifiedCoord()) {
918 NS_ASSERTION(col_width
== colFrame
->GetPrefCoord(),
919 "wrong width assigned");
920 if (col_width
!= 0) {
921 if (space
== nscoord_MAX
) {
922 basis
.c
-= col_width
;
923 col_width
= nscoord_MAX
;
925 float c
= float(space
) / float(basis
.c
);
926 basis
.c
-= col_width
;
927 col_width
+= NSToCoordRound(float(col_width
) * c
);
932 case FLEX_FLEX_LARGE_ZERO
:
933 NS_ASSERTION(aWidthType
== BTLS_FINAL_WIDTH
,
934 "FLEX_FLEX_LARGE_ZERO only should be hit "
935 "when we're setting final width.");
937 !colFrame
->GetHasSpecifiedCoord() &&
938 mTableFrame
->ColumnHasCellSpacingBefore(col
)) {
940 NS_ASSERTION(col_width
== 0 &&
941 colFrame
->GetPrefCoord() == 0,
942 "Since we're in FLEX_FLEX_LARGE_ZERO case, "
943 "all auto-width cols should have zero pref "
945 float c
= float(space
) / float(basis
.c
);
946 col_width
+= NSToCoordRound(c
);
950 case FLEX_FIXED_LARGE
:
952 NS_ASSERTION(col_width
== colFrame
->GetPrefCoord(),
953 "wrong width assigned");
954 NS_ASSERTION(colFrame
->GetHasSpecifiedCoord() ||
955 colFrame
->GetPrefCoord() == 0,
957 if (col_width
!= 0) {
958 float c
= float(space
) / float(basis
.c
);
959 basis
.c
-= col_width
;
960 col_width
+= NSToCoordRound(float(col_width
) * c
);
965 NS_ASSERTION(pct
!= 0.0f
|| colFrame
->GetPrefCoord() == 0,
968 float c
= float(space
) / basis
.f
;
969 col_width
+= NSToCoordRound(pct
* c
);
975 float c
= float(space
) / float(basis
.c
);
976 col_width
+= NSToCoordRound(c
);
982 // Only subtract from space if it's a real number.
983 if (space
!= nscoord_MAX
) {
984 NS_ASSERTION(col_width
!= nscoord_MAX
,
985 "How is col_width nscoord_MAX if space isn't?");
986 NS_ASSERTION(col_width_before_adjust
!= nscoord_MAX
,
987 "How is col_width_before_adjust nscoord_MAX if space isn't?");
988 space
-= col_width
- col_width_before_adjust
;
991 NS_ASSERTION(col_width
>= colFrame
->GetMinCoord(),
992 "assigned width smaller than min");
994 // Apply the new width
995 switch (aWidthType
) {
998 // Note: AddSpanCoords requires both a min and pref width.
999 // For the pref width, we'll just pass in our computed
1000 // min width, because the real pref width will be at least
1002 colFrame
->AddSpanCoords(col_width
, col_width
,
1003 aSpanHasSpecifiedWidth
);
1006 case BTLS_PREF_WIDTH
:
1008 // Note: AddSpanCoords requires both a min and pref width.
1009 // For the min width, we'll just pass in 0, because
1010 // the real min width will be at least 0
1011 colFrame
->AddSpanCoords(0, col_width
,
1012 aSpanHasSpecifiedWidth
);
1015 case BTLS_FINAL_WIDTH
:
1017 nscoord old_final
= colFrame
->GetFinalWidth();
1018 colFrame
->SetFinalWidth(col_width
);
1020 if (old_final
!= col_width
)
1021 mTableFrame
->DidResizeColumns();
1026 NS_ASSERTION((space
== 0 || space
== nscoord_MAX
) &&
1027 ((l2t
== FLEX_PCT_LARGE
)
1028 ? (-0.001f
< basis
.f
&& basis
.f
< 0.001f
)
1029 : (basis
.c
== 0 || basis
.c
== nscoord_MAX
)),
1030 "didn't subtract all that we added");