Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / layout / tables / BasicTableLayoutStrategy.cpp
bloba2c2ee2e9ade5f0e6108bfe38372c36880edce4e
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 #undef DEBUG_TABLE_STRATEGY
53 BasicTableLayoutStrategy::BasicTableLayoutStrategy(nsTableFrame *aTableFrame)
54 : nsITableLayoutStrategy(nsITableLayoutStrategy::Auto)
55 , mTableFrame(aTableFrame)
57 MarkIntrinsicWidthsDirty();
60 /* virtual */
61 BasicTableLayoutStrategy::~BasicTableLayoutStrategy()
65 /* virtual */ nscoord
66 BasicTableLayoutStrategy::GetMinWidth(nsIRenderingContext* aRenderingContext)
68 DISPLAY_MIN_WIDTH(mTableFrame, mMinWidth);
69 if (mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN)
70 ComputeIntrinsicWidths(aRenderingContext);
71 return mMinWidth;
74 /* virtual */ nscoord
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)
91 , minCoord(aMinCoord)
92 , prefCoord(aPrefCoord)
93 , prefPercent(aPrefPercent)
97 PRBool hasSpecifiedWidth;
98 nscoord minCoord;
99 nscoord prefCoord;
100 float prefPercent;
103 // Used for both column and cell calculations. The parts needed only
104 // for cells are skipped when aIsCell is false.
105 static CellWidthInfo
106 GetWidthInfo(nsIRenderingContext *aRenderingContext,
107 nsIFrame *aFrame, PRBool aIsCell)
109 nscoord minCoord, prefCoord;
110 if (aIsCell) {
111 minCoord = aFrame->GetMinWidth(aRenderingContext);
112 prefCoord = aFrame->GetPrefWidth(aRenderingContext);
113 } else {
114 minCoord = 0;
115 prefCoord = 0;
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)) {
138 minCoord = w;
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
148 break;
149 case NS_STYLE_WIDTH_MIN_CONTENT:
150 prefCoord = minCoord;
151 break;
152 case NS_STYLE_WIDTH_FIT_CONTENT:
153 case NS_STYLE_WIDTH_AVAILABLE:
154 // act just like 'width: auto'
155 break;
156 default:
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) {
175 nscoord w =
176 nsLayoutUtils::ComputeWidthValue(aRenderingContext, aFrame,
177 0, 0, 0, maxWidth);
178 if (w < minCoord)
179 minCoord = w;
180 if (w < prefCoord)
181 prefCoord = w;
182 } else if (unit == eStyleUnit_Percent) {
183 float p = stylePos->mMaxWidth.GetPercentValue();
184 if (p < prefPercent)
185 prefPercent = p;
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) {
200 nscoord w =
201 nsLayoutUtils::ComputeWidthValue(aRenderingContext, aFrame,
202 0, 0, 0, minWidth);
203 if (w > minCoord)
204 minCoord = w;
205 if (w > prefCoord)
206 prefCoord = w;
207 } else if (unit == eStyleUnit_Percent) {
208 float p = stylePos->mMinWidth.GetPercentValue();
209 if (p > prefPercent)
210 prefPercent = p;
213 // XXX Should col frame have border/padding considered?
214 if (aIsCell) {
215 nsIFrame::IntrinsicWidthOffsetData offsets =
216 aFrame->IntrinsicWidthOffsets(aRenderingContext);
217 // XXX Should we ignore percentage padding?
218 nscoord add = offsets.hPadding + offsets.hBorder;
219 minCoord += add;
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,
235 nsIFrame *aFrame)
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
245 * browsers are).
247 void
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*
256 // a colspan.
257 PRInt32 col, col_end;
258 for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) {
259 nsTableColFrame *colFrame = tableFrame->GetColFrame(col);
260 if (!colFrame) {
261 NS_ERROR("column frames out of sync with cell map");
262 continue;
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
286 // colspans.
287 nsCellMapColumnIterator columnIter(cellMap, col);
288 PRInt32 row, colSpan;
289 nsTableCellFrame* cellFrame;
290 while ((cellFrame = columnIter.GetNextFrame(&row, &colSpan))) {
291 if (colSpan > 1) {
292 spanningCells.AddCell(colSpan, row, col);
293 continue;
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());
307 #endif
309 #ifdef DEBUG_TABLE_STRATEGY
310 printf("ComputeColumnIntrinsicWidths single\n");
311 mTableFrame->Dump(PR_FALSE, PR_TRUE, PR_FALSE);
312 #endif
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*
320 // members.
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;
333 PRInt32 colSpan;
334 while ((item = spanningCells.GetNext(&colSpan))) {
335 NS_ASSERTION(colSpan > 1,
336 "cell should not have been put in spanning cell sorter");
337 do {
338 PRInt32 row = item->row;
339 col = item->col;
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,
351 col, colSpan);
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);
364 if (!colFrame) {
365 NS_ERROR("column frames out of sync with cell map");
366 continue;
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());
377 #endif
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);
389 if (!colFrame) {
390 NS_ERROR("column frames out of sync with cell map");
391 continue;
394 colFrame->AdjustPrefPercent(&pct_used);
397 #ifdef DEBUG_TABLE_STRATEGY
398 printf("ComputeColumnIntrinsicWidths spanning\n");
399 mTableFrame->Dump(PR_FALSE, PR_TRUE, PR_FALSE);
400 #endif
403 void
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);
418 if (!colFrame) {
419 NS_ERROR("column frames out of sync with cell map");
420 continue;
422 if (mTableFrame->ColumnHasCellSpacingBefore(col)) {
423 add += spacing;
425 min += colFrame->GetMinCoord();
426 pref = NSCoordSaturatingAdd(pref, colFrame->GetPrefCoord());
428 // Percentages are of the table, so we have to reverse them for
429 // intrinsic widths.
430 float p = colFrame->GetPrefPercent();
431 if (p > 0.0f) {
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;
439 pct_total += p;
440 } else {
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
449 // *other* columns.
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
464 // nested tables!)
466 } else {
467 nscoord large_pct_pref =
468 (nonpct_pref_total == nscoord_MAX ?
469 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
476 if (colCount > 0) {
477 min += add;
478 pref = NSCoordSaturatingAdd(pref, add);
479 pref_pct_expand = NSCoordSaturatingAdd(pref_pct_expand, add);
482 mMinWidth = min;
483 mPrefWidth = pref;
484 mPrefWidthPctExpand = pref_pct_expand;
487 /* virtual */ void
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;
496 /* virtual */ void
497 BasicTableLayoutStrategy::ComputeColumnWidths(const nsHTMLReflowState& aReflowState)
499 nscoord width = aReflowState.ComputedWidth();
501 if (mLastCalcWidth == width)
502 return;
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();
517 if (colCount <= 0)
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);
525 #endif
528 void
529 BasicTableLayoutStrategy::DistributePctWidthToColumns(float aSpanPrefPct,
530 PRInt32 aFirstCol,
531 PRInt32 aColCount)
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);
542 if (!scolFrame) {
543 NS_ERROR("column frames out of sync with cell map");
544 continue;
546 float scolPct = scolFrame->GetPrefPercent();
547 if (scolPct == 0.0f) {
548 nonPctTotalPrefWidth += scolFrame->GetPrefCoord();
549 ++nonPctColCount;
550 } else {
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
558 return;
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);
567 if (!scolFrame) {
568 NS_ERROR("column frames out of sync with cell map");
569 continue;
572 if (scolFrame->GetPrefPercent() == 0.0f) {
573 NS_ASSERTION((!spanHasNonPctPref ||
574 nonPctTotalPrefWidth != 0) &&
575 nonPctColCount != 0,
576 "should not be zero if we haven't allocated "
577 "all pref percent");
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));
586 } else {
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();
597 --nonPctColCount;
599 if (!aSpanPrefPct) {
600 // No more span-percent-width to distribute --> we're done.
601 NS_ASSERTION(spanHasNonPctPref ?
602 nonPctTotalPrefWidth == 0 :
603 nonPctColCount == 0,
604 "No more pct width to distribute, but there are "
605 "still cols that need some.");
606 return;
612 void
613 BasicTableLayoutStrategy::DistributeWidthToColumns(nscoord aWidth,
614 PRInt32 aFirstCol,
615 PRInt32 aColCount,
616 BtlsWidthType aWidthType,
617 PRBool aSpanHasSpecifiedWidth)
619 NS_ASSERTION(aWidthType != BTLS_FINAL_WIDTH ||
620 (aFirstCol == 0 &&
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)) {
632 subtract += spacing;
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,
697 guess_min_pct = 0,
698 guess_min_spec = 0,
699 guess_pref = 0,
700 total_flex_pref = 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;
706 PRInt32 col;
707 for (col = aFirstCol; col < aFirstCol + aColCount; ++col) {
708 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
709 if (!colFrame) {
710 NS_ERROR("column frames out of sync with cell map");
711 continue;
713 nscoord min_width = colFrame->GetMinCoord();
714 guess_min += min_width;
715 if (colFrame->GetPrefPercent() != 0.0f) {
716 float pct = colFrame->GetPrefPercent();
717 total_pct += pct;
718 nscoord val = nscoord(float(aWidth) * pct);
719 if (val < min_width)
720 val = min_width;
721 guess_min_pct += val;
722 guess_pref = NSCoordSaturatingAdd(guess_pref, val);
723 } else {
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
732 // loop
733 nscoord delta = NSCoordSaturatingSubtract(pref_width,
734 min_width, 0);
735 guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, delta);
736 total_fixed_pref = NSCoordSaturatingAdd(total_fixed_pref,
737 pref_width);
738 } else if (pref_width == 0) {
739 if (aWidthType == BTLS_FINAL_WIDTH &&
740 mTableFrame->ColumnHasCellSpacingBefore(col)) {
741 ++numNonSpecZeroWidthCols;
743 } else {
744 total_flex_pref = NSCoordSaturatingAdd(total_flex_pref,
745 pref_width);
749 guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, guess_min_pct);
751 // Determine what we're flexing:
752 enum Loop2Type {
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)
763 Loop2Type l2t;
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
768 union {
769 nscoord c;
770 float f;
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.
775 return;
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,
788 nscoord_MAX);
789 } else {
790 l2t = FLEX_FLEX_SMALL;
791 space = aWidth - guess_min_spec;
792 basis.c = NSCoordSaturatingSubtract(guess_pref, guess_min_spec,
793 nscoord_MAX);
795 } else {
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;
811 basis.f = total_pct;
812 } else {
813 l2t = FLEX_ALL_LARGE;
814 basis.c = aColCount;
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",
822 aColCount, aWidth,
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);
826 #endif
828 for (col = aFirstCol; col < aFirstCol + aColCount; ++col) {
829 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
830 if (!colFrame) {
831 NS_ERROR("column frames out of sync with cell map");
832 continue;
834 nscoord col_width;
836 float pct = colFrame->GetPrefPercent();
837 if (pct != 0.0f) {
838 col_width = nscoord(float(aWidth) * pct);
839 nscoord col_min = colFrame->GetMinCoord();
840 if (col_width < col_min)
841 col_width = col_min;
842 } else {
843 col_width = colFrame->GetPrefCoord();
846 nscoord col_width_before_adjust = col_width;
848 switch (l2t) {
849 case FLEX_PCT_SMALL:
850 col_width = col_width_before_adjust = colFrame->GetMinCoord();
851 if (pct != 0.0f) {
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);
860 break;
861 case FLEX_FIXED_SMALL:
862 if (pct == 0.0f) {
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);
875 } else
876 col_width = col_width_before_adjust =
877 colFrame->GetMinCoord();
879 break;
880 case FLEX_FLEX_SMALL:
881 if (pct == 0.0f &&
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;
903 } else {
904 c = 0.0f;
907 basis.c = NSCoordSaturatingSubtract(basis.c,
908 pref_minus_min,
909 nscoord_MAX);
910 col_width += NSToCoordRound(
911 float(pref_minus_min) * c);
914 break;
915 case FLEX_FLEX_LARGE:
916 if (pct == 0.0f &&
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;
924 } else {
925 float c = float(space) / float(basis.c);
926 basis.c -= col_width;
927 col_width += NSToCoordRound(float(col_width) * c);
931 break;
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.");
936 if (pct == 0.0f &&
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 "
944 "width.");
945 float c = float(space) / float(basis.c);
946 col_width += NSToCoordRound(c);
947 --basis.c;
949 break;
950 case FLEX_FIXED_LARGE:
951 if (pct == 0.0f) {
952 NS_ASSERTION(col_width == colFrame->GetPrefCoord(),
953 "wrong width assigned");
954 NS_ASSERTION(colFrame->GetHasSpecifiedCoord() ||
955 colFrame->GetPrefCoord() == 0,
956 "wrong case");
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);
963 break;
964 case FLEX_PCT_LARGE:
965 NS_ASSERTION(pct != 0.0f || colFrame->GetPrefCoord() == 0,
966 "wrong case");
967 if (pct != 0.0f) {
968 float c = float(space) / basis.f;
969 col_width += NSToCoordRound(pct * c);
970 basis.f -= pct;
972 break;
973 case FLEX_ALL_LARGE:
975 float c = float(space) / float(basis.c);
976 col_width += NSToCoordRound(c);
977 --basis.c;
979 break;
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) {
996 case BTLS_MIN_WIDTH:
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
1001 // as big
1002 colFrame->AddSpanCoords(col_width, col_width,
1003 aSpanHasSpecifiedWidth);
1005 break;
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);
1014 break;
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();
1023 break;
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");