Merge mozilla-central and tracemonkey. (a=blockers)
[mozilla-central.git] / layout / tables / BasicTableLayoutStrategy.cpp
blob54584dff294c6b7da4d78eb4cbd817d8626e0d8e
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
14 * License.
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.
22 * Contributor(s):
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();
62 /* virtual */
63 BasicTableLayoutStrategy::~BasicTableLayoutStrategy()
67 /* virtual */ nscoord
68 BasicTableLayoutStrategy::GetMinWidth(nsIRenderingContext* aRenderingContext)
70 DISPLAY_MIN_WIDTH(mTableFrame, mMinWidth);
71 if (mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN)
72 ComputeIntrinsicWidths(aRenderingContext);
73 return mMinWidth;
76 /* virtual */ nscoord
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)
93 , minCoord(aMinCoord)
94 , prefCoord(aPrefCoord)
95 , prefPercent(aPrefPercent)
99 PRBool hasSpecifiedWidth;
100 nscoord minCoord;
101 nscoord prefCoord;
102 float prefPercent;
105 // Used for both column and cell calculations. The parts needed only
106 // for cells are skipped when aIsCell is false.
107 static CellWidthInfo
108 GetWidthInfo(nsIRenderingContext *aRenderingContext,
109 nsIFrame *aFrame, PRBool aIsCell)
111 nscoord minCoord, prefCoord;
112 if (aIsCell) {
113 minCoord = aFrame->GetMinWidth(aRenderingContext);
114 prefCoord = aFrame->GetPrefWidth(aRenderingContext);
115 } else {
116 minCoord = 0;
117 prefCoord = 0;
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)) {
144 minCoord = w;
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
154 break;
155 case NS_STYLE_WIDTH_MIN_CONTENT:
156 prefCoord = minCoord;
157 break;
158 case NS_STYLE_WIDTH_FIT_CONTENT:
159 case NS_STYLE_WIDTH_AVAILABLE:
160 // act just like 'width: auto'
161 break;
162 default:
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) {
181 nscoord w =
182 nsLayoutUtils::ComputeWidthValue(aRenderingContext, aFrame,
183 0, 0, 0, maxWidth);
184 if (w < minCoord)
185 minCoord = w;
186 if (w < prefCoord)
187 prefCoord = w;
188 } else if (unit == eStyleUnit_Percent) {
189 float p = stylePos->mMaxWidth.GetPercentValue();
190 if (p < prefPercent)
191 prefPercent = p;
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) {
207 nscoord w =
208 nsLayoutUtils::ComputeWidthValue(aRenderingContext, aFrame,
209 0, 0, 0, minWidth);
210 if (w > minCoord)
211 minCoord = w;
212 if (w > prefCoord)
213 prefCoord = w;
214 } else if (unit == eStyleUnit_Percent) {
215 float p = stylePos->mMinWidth.GetPercentValue();
216 if (p > prefPercent)
217 prefPercent = p;
219 // treat calc() on min-width just like '0'.
221 // XXX Should col frame have border/padding considered?
222 if (aIsCell) {
223 nsIFrame::IntrinsicWidthOffsetData offsets =
224 aFrame->IntrinsicWidthOffsets(aRenderingContext);
225 // XXX Should we ignore percentage padding?
226 nscoord add = offsets.hPadding + offsets.hBorder;
227 minCoord += add;
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,
243 nsIFrame *aFrame)
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
253 * browsers are).
255 void
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*
264 // a colspan.
265 PRInt32 col, col_end;
266 for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) {
267 nsTableColFrame *colFrame = tableFrame->GetColFrame(col);
268 if (!colFrame) {
269 NS_ERROR("column frames out of sync with cell map");
270 continue;
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
298 // colspans.
299 nsCellMapColumnIterator columnIter(cellMap, col);
300 PRInt32 row, colSpan;
301 nsTableCellFrame* cellFrame;
302 while ((cellFrame = columnIter.GetNextFrame(&row, &colSpan))) {
303 if (colSpan > 1) {
304 spanningCells.AddCell(colSpan, row, col);
305 continue;
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());
319 #endif
321 #ifdef DEBUG_TABLE_STRATEGY
322 printf("ComputeColumnIntrinsicWidths single\n");
323 mTableFrame->Dump(PR_FALSE, PR_TRUE, PR_FALSE);
324 #endif
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*
332 // members.
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;
345 PRInt32 colSpan;
346 while ((item = spanningCells.GetNext(&colSpan))) {
347 NS_ASSERTION(colSpan > 1,
348 "cell should not have been put in spanning cell sorter");
349 do {
350 PRInt32 row = item->row;
351 col = item->col;
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,
363 col, colSpan);
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);
376 if (!colFrame) {
377 NS_ERROR("column frames out of sync with cell map");
378 continue;
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());
389 #endif
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);
401 if (!colFrame) {
402 NS_ERROR("column frames out of sync with cell map");
403 continue;
406 colFrame->AdjustPrefPercent(&pct_used);
409 #ifdef DEBUG_TABLE_STRATEGY
410 printf("ComputeColumnIntrinsicWidths spanning\n");
411 mTableFrame->Dump(PR_FALSE, PR_TRUE, PR_FALSE);
412 #endif
415 void
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);
430 if (!colFrame) {
431 NS_ERROR("column frames out of sync with cell map");
432 continue;
434 if (mTableFrame->ColumnHasCellSpacingBefore(col)) {
435 add += spacing;
437 min += colFrame->GetMinCoord();
438 pref = NSCoordSaturatingAdd(pref, colFrame->GetPrefCoord());
440 // Percentages are of the table, so we have to reverse them for
441 // intrinsic widths.
442 float p = colFrame->GetPrefPercent();
443 if (p > 0.0f) {
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;
451 pct_total += p;
452 } else {
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
461 // *other* columns.
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
476 // nested tables!)
478 } else {
479 nscoord large_pct_pref =
480 (nonpct_pref_total == nscoord_MAX ?
481 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
488 if (colCount > 0) {
489 min += add;
490 pref = NSCoordSaturatingAdd(pref, add);
491 pref_pct_expand = NSCoordSaturatingAdd(pref_pct_expand, add);
494 mMinWidth = min;
495 mPrefWidth = pref;
496 mPrefWidthPctExpand = pref_pct_expand;
499 /* virtual */ void
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;
508 /* virtual */ void
509 BasicTableLayoutStrategy::ComputeColumnWidths(const nsHTMLReflowState& aReflowState)
511 nscoord width = aReflowState.ComputedWidth();
513 if (mLastCalcWidth == width)
514 return;
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();
529 if (colCount <= 0)
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);
537 #endif
540 void
541 BasicTableLayoutStrategy::DistributePctWidthToColumns(float aSpanPrefPct,
542 PRInt32 aFirstCol,
543 PRInt32 aColCount)
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);
554 if (!scolFrame) {
555 NS_ERROR("column frames out of sync with cell map");
556 continue;
558 float scolPct = scolFrame->GetPrefPercent();
559 if (scolPct == 0.0f) {
560 nonPctTotalPrefWidth += scolFrame->GetPrefCoord();
561 ++nonPctColCount;
562 } else {
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
570 return;
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);
579 if (!scolFrame) {
580 NS_ERROR("column frames out of sync with cell map");
581 continue;
584 if (scolFrame->GetPrefPercent() == 0.0f) {
585 NS_ASSERTION((!spanHasNonPctPref ||
586 nonPctTotalPrefWidth != 0) &&
587 nonPctColCount != 0,
588 "should not be zero if we haven't allocated "
589 "all pref percent");
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));
598 } else {
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();
609 --nonPctColCount;
611 if (!aSpanPrefPct) {
612 // No more span-percent-width to distribute --> we're done.
613 NS_ASSERTION(spanHasNonPctPref ?
614 nonPctTotalPrefWidth == 0 :
615 nonPctColCount == 0,
616 "No more pct width to distribute, but there are "
617 "still cols that need some.");
618 return;
624 void
625 BasicTableLayoutStrategy::DistributeWidthToColumns(nscoord aWidth,
626 PRInt32 aFirstCol,
627 PRInt32 aColCount,
628 BtlsWidthType aWidthType,
629 PRBool aSpanHasSpecifiedWidth)
631 NS_ASSERTION(aWidthType != BTLS_FINAL_WIDTH ||
632 (aFirstCol == 0 &&
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)) {
644 subtract += spacing;
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,
709 guess_min_pct = 0,
710 guess_min_spec = 0,
711 guess_pref = 0,
712 total_flex_pref = 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;
718 PRInt32 col;
719 nsTableCellMap *cellMap = mTableFrame->GetCellMap();
720 for (col = aFirstCol; col < aFirstCol + aColCount; ++col) {
721 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
722 if (!colFrame) {
723 NS_ERROR("column frames out of sync with cell map");
724 continue;
726 nscoord min_width = colFrame->GetMinCoord();
727 guess_min += min_width;
728 if (colFrame->GetPrefPercent() != 0.0f) {
729 float pct = colFrame->GetPrefPercent();
730 total_pct += pct;
731 nscoord val = nscoord(float(aWidth) * pct);
732 if (val < min_width)
733 val = min_width;
734 guess_min_pct += val;
735 guess_pref = NSCoordSaturatingAdd(guess_pref, val);
736 } else {
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
745 // loop
746 nscoord delta = NSCoordSaturatingSubtract(pref_width,
747 min_width, 0);
748 guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, delta);
749 total_fixed_pref = NSCoordSaturatingAdd(total_fixed_pref,
750 pref_width);
751 } else if (pref_width == 0) {
752 if (aWidthType == BTLS_FINAL_WIDTH &&
753 cellMap->GetNumCellsOriginatingInCol(col) > 0) {
754 ++numNonSpecZeroWidthCols;
756 } else {
757 total_flex_pref = NSCoordSaturatingAdd(total_flex_pref,
758 pref_width);
762 guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, guess_min_pct);
764 // Determine what we're flexing:
765 enum Loop2Type {
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)
776 Loop2Type l2t;
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
781 union {
782 nscoord c;
783 float f;
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.
788 return;
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,
801 nscoord_MAX);
802 } else {
803 l2t = FLEX_FLEX_SMALL;
804 space = aWidth - guess_min_spec;
805 basis.c = NSCoordSaturatingSubtract(guess_pref, guess_min_spec,
806 nscoord_MAX);
808 } else {
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;
824 basis.f = total_pct;
825 } else {
826 l2t = FLEX_ALL_LARGE;
827 basis.c = aColCount;
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",
835 aColCount, aWidth,
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);
839 #endif
841 for (col = aFirstCol; col < aFirstCol + aColCount; ++col) {
842 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
843 if (!colFrame) {
844 NS_ERROR("column frames out of sync with cell map");
845 continue;
847 nscoord col_width;
849 float pct = colFrame->GetPrefPercent();
850 if (pct != 0.0f) {
851 col_width = nscoord(float(aWidth) * pct);
852 nscoord col_min = colFrame->GetMinCoord();
853 if (col_width < col_min)
854 col_width = col_min;
855 } else {
856 col_width = colFrame->GetPrefCoord();
859 nscoord col_width_before_adjust = col_width;
861 switch (l2t) {
862 case FLEX_PCT_SMALL:
863 col_width = col_width_before_adjust = colFrame->GetMinCoord();
864 if (pct != 0.0f) {
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);
873 break;
874 case FLEX_FIXED_SMALL:
875 if (pct == 0.0f) {
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);
888 } else
889 col_width = col_width_before_adjust =
890 colFrame->GetMinCoord();
892 break;
893 case FLEX_FLEX_SMALL:
894 if (pct == 0.0f &&
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;
916 } else {
917 c = 0.0f;
920 basis.c = NSCoordSaturatingSubtract(basis.c,
921 pref_minus_min,
922 nscoord_MAX);
923 col_width += NSToCoordRound(
924 float(pref_minus_min) * c);
927 break;
928 case FLEX_FLEX_LARGE:
929 if (pct == 0.0f &&
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;
937 } else {
938 float c = float(space) / float(basis.c);
939 basis.c -= col_width;
940 col_width += NSToCoordRound(float(col_width) * c);
944 break;
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.");
949 if (pct == 0.0f &&
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 "
957 "width.");
958 float c = float(space) / float(basis.c);
959 col_width += NSToCoordRound(c);
960 --basis.c;
962 break;
963 case FLEX_FIXED_LARGE:
964 if (pct == 0.0f) {
965 NS_ASSERTION(col_width == colFrame->GetPrefCoord(),
966 "wrong width assigned");
967 NS_ASSERTION(colFrame->GetHasSpecifiedCoord() ||
968 colFrame->GetPrefCoord() == 0,
969 "wrong case");
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);
976 break;
977 case FLEX_PCT_LARGE:
978 NS_ASSERTION(pct != 0.0f || colFrame->GetPrefCoord() == 0,
979 "wrong case");
980 if (pct != 0.0f) {
981 float c = float(space) / basis.f;
982 col_width += NSToCoordRound(pct * c);
983 basis.f -= pct;
985 break;
986 case FLEX_ALL_LARGE:
988 float c = float(space) / float(basis.c);
989 col_width += NSToCoordRound(c);
990 --basis.c;
992 break;
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
1014 // as big
1015 colFrame->AddSpanCoords(col_width, col_width,
1016 aSpanHasSpecifiedWidth);
1018 break;
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);
1027 break;
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();
1036 break;
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");