Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 // vim:cindent:ts=4:et:sw=4:
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
8 * Web-compatible algorithms that determine column and table widths,
9 * used for CSS2's 'table-layout: auto'.
10 */
12 #include "BasicTableLayoutStrategy.h"
13 #include "nsTableFrame.h"
14 #include "nsTableCellFrame.h"
15 #include "nsLayoutUtils.h"
16 #include "nsGkAtoms.h"
17 #include "SpanningCellSorter.h"
18 #include <algorithm>
20 using namespace mozilla;
21 using namespace mozilla::layout;
23 namespace css = mozilla::css;
25 #undef DEBUG_TABLE_STRATEGY
27 BasicTableLayoutStrategy::BasicTableLayoutStrategy(nsTableFrame *aTableFrame)
28 : nsITableLayoutStrategy(nsITableLayoutStrategy::Auto)
29 , mTableFrame(aTableFrame)
30 {
31 MarkIntrinsicWidthsDirty();
32 }
34 /* virtual */
35 BasicTableLayoutStrategy::~BasicTableLayoutStrategy()
36 {
37 }
39 /* virtual */ nscoord
40 BasicTableLayoutStrategy::GetMinWidth(nsRenderingContext* aRenderingContext)
41 {
42 DISPLAY_MIN_WIDTH(mTableFrame, mMinWidth);
43 if (mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN)
44 ComputeIntrinsicWidths(aRenderingContext);
45 return mMinWidth;
46 }
48 /* virtual */ nscoord
49 BasicTableLayoutStrategy::GetPrefWidth(nsRenderingContext* aRenderingContext,
50 bool aComputingSize)
51 {
52 DISPLAY_PREF_WIDTH(mTableFrame, mPrefWidth);
53 NS_ASSERTION((mPrefWidth == NS_INTRINSIC_WIDTH_UNKNOWN) ==
54 (mPrefWidthPctExpand == NS_INTRINSIC_WIDTH_UNKNOWN),
55 "dirtyness out of sync");
56 if (mPrefWidth == NS_INTRINSIC_WIDTH_UNKNOWN)
57 ComputeIntrinsicWidths(aRenderingContext);
58 return aComputingSize ? mPrefWidthPctExpand : mPrefWidth;
59 }
61 struct CellWidthInfo {
62 CellWidthInfo(nscoord aMinCoord, nscoord aPrefCoord,
63 float aPrefPercent, bool aHasSpecifiedWidth)
64 : hasSpecifiedWidth(aHasSpecifiedWidth)
65 , minCoord(aMinCoord)
66 , prefCoord(aPrefCoord)
67 , prefPercent(aPrefPercent)
68 {
69 }
71 bool hasSpecifiedWidth;
72 nscoord minCoord;
73 nscoord prefCoord;
74 float prefPercent;
75 };
77 // Used for both column and cell calculations. The parts needed only
78 // for cells are skipped when aIsCell is false.
79 static CellWidthInfo
80 GetWidthInfo(nsRenderingContext *aRenderingContext,
81 nsIFrame *aFrame, bool aIsCell)
82 {
83 nscoord minCoord, prefCoord;
84 const nsStylePosition *stylePos = aFrame->StylePosition();
85 bool isQuirks = aFrame->PresContext()->CompatibilityMode() ==
86 eCompatibility_NavQuirks;
87 nscoord boxSizingToBorderEdge = 0;
88 if (aIsCell) {
89 // If aFrame is a container for font size inflation, then shrink
90 // wrapping inside of it should not apply font size inflation.
91 AutoMaybeDisableFontInflation an(aFrame);
93 minCoord = aFrame->GetMinWidth(aRenderingContext);
94 prefCoord = aFrame->GetPrefWidth(aRenderingContext);
95 // Until almost the end of this function, minCoord and prefCoord
96 // represent the box-sizing based width values (which mean they
97 // should include horizontal padding and border width when
98 // box-sizing is set to border-box).
99 // Note that this function returns border-box width, we add the
100 // outer edges near the end of this function.
102 // XXX Should we ignore percentage padding?
103 nsIFrame::IntrinsicWidthOffsetData offsets = aFrame->IntrinsicWidthOffsets(aRenderingContext);
105 // In quirks mode, table cell width should be content-box,
106 // but height should be border box.
107 // Because of this historic anomaly, we do not use quirk.css.
108 // (We can't specify one value of box-sizing for width and another
109 // for height).
110 // For this reason, we also do not use box-sizing for just one of
111 // them, as this may be confusing.
112 if (isQuirks) {
113 boxSizingToBorderEdge = offsets.hPadding + offsets.hBorder;
114 }
115 else {
116 switch (stylePos->mBoxSizing) {
117 case NS_STYLE_BOX_SIZING_CONTENT:
118 boxSizingToBorderEdge = offsets.hPadding + offsets.hBorder;
119 break;
120 case NS_STYLE_BOX_SIZING_PADDING:
121 minCoord += offsets.hPadding;
122 prefCoord += offsets.hPadding;
123 boxSizingToBorderEdge = offsets.hBorder;
124 break;
125 default:
126 // NS_STYLE_BOX_SIZING_BORDER
127 minCoord += offsets.hPadding + offsets.hBorder;
128 prefCoord += offsets.hPadding + offsets.hBorder;
129 break;
130 }
131 }
132 } else {
133 minCoord = 0;
134 prefCoord = 0;
135 }
136 float prefPercent = 0.0f;
137 bool hasSpecifiedWidth = false;
139 const nsStyleCoord &width = stylePos->mWidth;
140 nsStyleUnit unit = width.GetUnit();
141 // NOTE: We're ignoring calc() units with percentages here, for lack of a
142 // sensible idea for what to do with them. This means calc() with
143 // percentages is basically handled like 'auto' for table cells and
144 // columns.
145 if (width.ConvertsToLength()) {
146 hasSpecifiedWidth = true;
147 // Note: since ComputeWidthValue was designed to return content-box
148 // width, it will (in some cases) subtract the box-sizing edges.
149 // We prevent this unwanted behavior by calling it with
150 // aContentEdgeToBoxSizing and aBoxSizingToMarginEdge set to 0.
151 nscoord w = nsLayoutUtils::ComputeWidthValue(aRenderingContext,
152 aFrame, 0, 0, 0, width);
153 // Quirk: A cell with "nowrap" set and a coord value for the
154 // width which is bigger than the intrinsic minimum width uses
155 // that coord value as the minimum width.
156 // This is kept up-to-date with dynamic changes to nowrap by code in
157 // nsTableCellFrame::AttributeChanged
158 if (aIsCell && w > minCoord && isQuirks &&
159 aFrame->GetContent()->HasAttr(kNameSpaceID_None,
160 nsGkAtoms::nowrap)) {
161 minCoord = w;
162 }
163 prefCoord = std::max(w, minCoord);
164 } else if (unit == eStyleUnit_Percent) {
165 prefPercent = width.GetPercentValue();
166 } else if (unit == eStyleUnit_Enumerated && aIsCell) {
167 switch (width.GetIntValue()) {
168 case NS_STYLE_WIDTH_MAX_CONTENT:
169 // 'width' only affects pref width, not min
170 // width, so don't change anything
171 break;
172 case NS_STYLE_WIDTH_MIN_CONTENT:
173 prefCoord = minCoord;
174 break;
175 case NS_STYLE_WIDTH_FIT_CONTENT:
176 case NS_STYLE_WIDTH_AVAILABLE:
177 // act just like 'width: auto'
178 break;
179 default:
180 NS_NOTREACHED("unexpected enumerated value");
181 }
182 }
184 nsStyleCoord maxWidth(stylePos->mMaxWidth);
185 if (maxWidth.GetUnit() == eStyleUnit_Enumerated) {
186 if (!aIsCell || maxWidth.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE)
187 maxWidth.SetNoneValue();
188 else if (maxWidth.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT)
189 // for 'max-width', '-moz-fit-content' is like
190 // '-moz-max-content'
191 maxWidth.SetIntValue(NS_STYLE_WIDTH_MAX_CONTENT,
192 eStyleUnit_Enumerated);
193 }
194 unit = maxWidth.GetUnit();
195 // XXX To really implement 'max-width' well, we'd need to store
196 // it separately on the columns.
197 if (maxWidth.ConvertsToLength() || unit == eStyleUnit_Enumerated) {
198 nscoord w =
199 nsLayoutUtils::ComputeWidthValue(aRenderingContext, aFrame,
200 0, 0, 0, maxWidth);
201 if (w < minCoord)
202 minCoord = w;
203 if (w < prefCoord)
204 prefCoord = w;
205 } else if (unit == eStyleUnit_Percent) {
206 float p = stylePos->mMaxWidth.GetPercentValue();
207 if (p < prefPercent)
208 prefPercent = p;
209 }
210 // treat calc() with percentages on max-width just like 'none'.
212 nsStyleCoord minWidth(stylePos->mMinWidth);
213 if (minWidth.GetUnit() == eStyleUnit_Enumerated) {
214 if (!aIsCell || minWidth.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE)
215 minWidth.SetCoordValue(0);
216 else if (minWidth.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT)
217 // for 'min-width', '-moz-fit-content' is like
218 // '-moz-min-content'
219 minWidth.SetIntValue(NS_STYLE_WIDTH_MIN_CONTENT,
220 eStyleUnit_Enumerated);
221 }
222 unit = minWidth.GetUnit();
223 if (minWidth.ConvertsToLength() || unit == eStyleUnit_Enumerated) {
224 nscoord w =
225 nsLayoutUtils::ComputeWidthValue(aRenderingContext, aFrame,
226 0, 0, 0, minWidth);
227 if (w > minCoord)
228 minCoord = w;
229 if (w > prefCoord)
230 prefCoord = w;
231 } else if (unit == eStyleUnit_Percent) {
232 float p = stylePos->mMinWidth.GetPercentValue();
233 if (p > prefPercent)
234 prefPercent = p;
235 }
236 // treat calc() with percentages on min-width just like '0'.
238 // XXX Should col frame have border/padding considered?
239 if (aIsCell) {
240 minCoord += boxSizingToBorderEdge;
241 prefCoord = NSCoordSaturatingAdd(prefCoord, boxSizingToBorderEdge);
242 }
244 return CellWidthInfo(minCoord, prefCoord, prefPercent, hasSpecifiedWidth);
245 }
247 static inline CellWidthInfo
248 GetCellWidthInfo(nsRenderingContext *aRenderingContext,
249 nsTableCellFrame *aCellFrame)
250 {
251 return GetWidthInfo(aRenderingContext, aCellFrame, true);
252 }
254 static inline CellWidthInfo
255 GetColWidthInfo(nsRenderingContext *aRenderingContext,
256 nsIFrame *aFrame)
257 {
258 return GetWidthInfo(aRenderingContext, aFrame, false);
259 }
262 /**
263 * The algorithm in this function, in addition to meeting the
264 * requirements of Web-compatibility, is also invariant under reordering
265 * of the rows within a table (something that most, but not all, other
266 * browsers are).
267 */
268 void
269 BasicTableLayoutStrategy::ComputeColumnIntrinsicWidths(nsRenderingContext* aRenderingContext)
270 {
271 nsTableFrame *tableFrame = mTableFrame;
272 nsTableCellMap *cellMap = tableFrame->GetCellMap();
274 mozilla::AutoStackArena arena;
275 SpanningCellSorter spanningCells;
277 // Loop over the columns to consider the columns and cells *without*
278 // a colspan.
279 int32_t col, col_end;
280 for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) {
281 nsTableColFrame *colFrame = tableFrame->GetColFrame(col);
282 if (!colFrame) {
283 NS_ERROR("column frames out of sync with cell map");
284 continue;
285 }
286 colFrame->ResetIntrinsics();
287 colFrame->ResetSpanIntrinsics();
289 // Consider the widths on the column.
290 CellWidthInfo colInfo = GetColWidthInfo(aRenderingContext, colFrame);
291 colFrame->AddCoords(colInfo.minCoord, colInfo.prefCoord,
292 colInfo.hasSpecifiedWidth);
293 colFrame->AddPrefPercent(colInfo.prefPercent);
295 // Consider the widths on the column-group. Note that we follow
296 // what the HTML spec says here, and make the width apply to
297 // each column in the group, not the group as a whole.
299 // If column has width, column-group doesn't override width.
300 if (colInfo.minCoord == 0 && colInfo.prefCoord == 0 &&
301 colInfo.prefPercent == 0.0f) {
302 NS_ASSERTION(colFrame->GetParent()->GetType() ==
303 nsGkAtoms::tableColGroupFrame,
304 "expected a column-group");
305 colInfo = GetColWidthInfo(aRenderingContext, colFrame->GetParent());
306 colFrame->AddCoords(colInfo.minCoord, colInfo.prefCoord,
307 colInfo.hasSpecifiedWidth);
308 colFrame->AddPrefPercent(colInfo.prefPercent);
309 }
311 // Consider the contents of and the widths on the cells without
312 // colspans.
313 nsCellMapColumnIterator columnIter(cellMap, col);
314 int32_t row, colSpan;
315 nsTableCellFrame* cellFrame;
316 while ((cellFrame = columnIter.GetNextFrame(&row, &colSpan))) {
317 if (colSpan > 1) {
318 spanningCells.AddCell(colSpan, row, col);
319 continue;
320 }
322 CellWidthInfo info = GetCellWidthInfo(aRenderingContext, cellFrame);
324 colFrame->AddCoords(info.minCoord, info.prefCoord,
325 info.hasSpecifiedWidth);
326 colFrame->AddPrefPercent(info.prefPercent);
327 }
328 #ifdef DEBUG_dbaron_off
329 printf("table %p col %d nonspan: min=%d pref=%d spec=%d pct=%f\n",
330 mTableFrame, col, colFrame->GetMinCoord(),
331 colFrame->GetPrefCoord(), colFrame->GetHasSpecifiedCoord(),
332 colFrame->GetPrefPercent());
333 #endif
334 }
335 #ifdef DEBUG_TABLE_STRATEGY
336 printf("ComputeColumnIntrinsicWidths single\n");
337 mTableFrame->Dump(false, true, false);
338 #endif
340 // Consider the cells with a colspan that we saved in the loop above
341 // into the spanning cell sorter. We consider these cells by seeing
342 // if they require adding to the widths resulting only from cells
343 // with a smaller colspan, and therefore we must process them sorted
344 // in increasing order by colspan. For each colspan group, we
345 // accumulate new values to accumulate in the column frame's Span*
346 // members.
347 //
348 // Considering things only relative to the widths resulting from
349 // cells with smaller colspans (rather than incrementally including
350 // the results from spanning cells, or doing spanning and
351 // non-spanning cells in a single pass) means that layout remains
352 // row-order-invariant and (except for percentage widths that add to
353 // more than 100%) column-order invariant.
354 //
355 // Starting with smaller colspans makes it more likely that we
356 // satisfy all the constraints given and don't distribute space to
357 // columns where we don't need it.
358 SpanningCellSorter::Item *item;
359 int32_t colSpan;
360 while ((item = spanningCells.GetNext(&colSpan))) {
361 NS_ASSERTION(colSpan > 1,
362 "cell should not have been put in spanning cell sorter");
363 do {
364 int32_t row = item->row;
365 col = item->col;
366 CellData *cellData = cellMap->GetDataAt(row, col);
367 NS_ASSERTION(cellData && cellData->IsOrig(),
368 "bogus result from spanning cell sorter");
370 nsTableCellFrame *cellFrame = cellData->GetCellFrame();
371 NS_ASSERTION(cellFrame, "bogus result from spanning cell sorter");
373 CellWidthInfo info = GetCellWidthInfo(aRenderingContext, cellFrame);
375 if (info.prefPercent > 0.0f) {
376 DistributePctWidthToColumns(info.prefPercent,
377 col, colSpan);
378 }
379 DistributeWidthToColumns(info.minCoord, col, colSpan,
380 BTLS_MIN_WIDTH, info.hasSpecifiedWidth);
381 DistributeWidthToColumns(info.prefCoord, col, colSpan,
382 BTLS_PREF_WIDTH, info.hasSpecifiedWidth);
383 } while ((item = item->next));
385 // Combine the results of the span analysis into the main results,
386 // for each increment of colspan.
388 for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) {
389 nsTableColFrame *colFrame = tableFrame->GetColFrame(col);
390 if (!colFrame) {
391 NS_ERROR("column frames out of sync with cell map");
392 continue;
393 }
395 colFrame->AccumulateSpanIntrinsics();
396 colFrame->ResetSpanIntrinsics();
398 #ifdef DEBUG_dbaron_off
399 printf("table %p col %d span %d: min=%d pref=%d spec=%d pct=%f\n",
400 mTableFrame, col, colSpan, colFrame->GetMinCoord(),
401 colFrame->GetPrefCoord(), colFrame->GetHasSpecifiedCoord(),
402 colFrame->GetPrefPercent());
403 #endif
404 }
405 }
407 // Prevent percentages from adding to more than 100% by (to be
408 // compatible with other browsers) treating any percentages that would
409 // increase the total percentage to more than 100% as the number that
410 // would increase it to only 100% (which is 0% if we've already hit
411 // 100%). This means layout depends on the order of columns.
412 float pct_used = 0.0f;
413 for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) {
414 nsTableColFrame *colFrame = tableFrame->GetColFrame(col);
415 if (!colFrame) {
416 NS_ERROR("column frames out of sync with cell map");
417 continue;
418 }
420 colFrame->AdjustPrefPercent(&pct_used);
421 }
423 #ifdef DEBUG_TABLE_STRATEGY
424 printf("ComputeColumnIntrinsicWidths spanning\n");
425 mTableFrame->Dump(false, true, false);
426 #endif
427 }
429 void
430 BasicTableLayoutStrategy::ComputeIntrinsicWidths(nsRenderingContext* aRenderingContext)
431 {
432 ComputeColumnIntrinsicWidths(aRenderingContext);
434 nsTableCellMap *cellMap = mTableFrame->GetCellMap();
435 nscoord min = 0, pref = 0, max_small_pct_pref = 0, nonpct_pref_total = 0;
436 float pct_total = 0.0f; // always from 0.0f - 1.0f
437 int32_t colCount = cellMap->GetColCount();
438 nscoord spacing = mTableFrame->GetCellSpacingX();
439 nscoord add = spacing; // add (colcount + 1) * spacing for columns
440 // where a cell originates
442 for (int32_t col = 0; col < colCount; ++col) {
443 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
444 if (!colFrame) {
445 NS_ERROR("column frames out of sync with cell map");
446 continue;
447 }
448 if (mTableFrame->ColumnHasCellSpacingBefore(col)) {
449 add += spacing;
450 }
451 min += colFrame->GetMinCoord();
452 pref = NSCoordSaturatingAdd(pref, colFrame->GetPrefCoord());
454 // Percentages are of the table, so we have to reverse them for
455 // intrinsic widths.
456 float p = colFrame->GetPrefPercent();
457 if (p > 0.0f) {
458 nscoord colPref = colFrame->GetPrefCoord();
459 nscoord new_small_pct_expand =
460 (colPref == nscoord_MAX ?
461 nscoord_MAX : nscoord(float(colPref) / p));
462 if (new_small_pct_expand > max_small_pct_pref) {
463 max_small_pct_pref = new_small_pct_expand;
464 }
465 pct_total += p;
466 } else {
467 nonpct_pref_total = NSCoordSaturatingAdd(nonpct_pref_total,
468 colFrame->GetPrefCoord());
469 }
470 }
472 nscoord pref_pct_expand = pref;
474 // Account for small percentages expanding the preferred width of
475 // *other* columns.
476 if (max_small_pct_pref > pref_pct_expand) {
477 pref_pct_expand = max_small_pct_pref;
478 }
480 // Account for large percentages expanding the preferred width of
481 // themselves. There's no need to iterate over the columns multiple
482 // times, since when there is such a need, the small percentage
483 // effect is bigger anyway. (I think!)
484 NS_ASSERTION(0.0f <= pct_total && pct_total <= 1.0f,
485 "column percentage widths not adjusted down to 100%");
486 if (pct_total == 1.0f) {
487 if (nonpct_pref_total > 0) {
488 pref_pct_expand = nscoord_MAX;
489 // XXX Or should I use some smaller value? (Test this using
490 // nested tables!)
491 }
492 } else {
493 nscoord large_pct_pref =
494 (nonpct_pref_total == nscoord_MAX ?
495 nscoord_MAX :
496 nscoord(float(nonpct_pref_total) / (1.0f - pct_total)));
497 if (large_pct_pref > pref_pct_expand)
498 pref_pct_expand = large_pct_pref;
499 }
501 // border-spacing isn't part of the basis for percentages
502 if (colCount > 0) {
503 min += add;
504 pref = NSCoordSaturatingAdd(pref, add);
505 pref_pct_expand = NSCoordSaturatingAdd(pref_pct_expand, add);
506 }
508 mMinWidth = min;
509 mPrefWidth = pref;
510 mPrefWidthPctExpand = pref_pct_expand;
511 }
513 /* virtual */ void
514 BasicTableLayoutStrategy::MarkIntrinsicWidthsDirty()
515 {
516 mMinWidth = NS_INTRINSIC_WIDTH_UNKNOWN;
517 mPrefWidth = NS_INTRINSIC_WIDTH_UNKNOWN;
518 mPrefWidthPctExpand = NS_INTRINSIC_WIDTH_UNKNOWN;
519 mLastCalcWidth = nscoord_MIN;
520 }
522 /* virtual */ void
523 BasicTableLayoutStrategy::ComputeColumnWidths(const nsHTMLReflowState& aReflowState)
524 {
525 nscoord width = aReflowState.ComputedWidth();
527 if (mLastCalcWidth == width)
528 return;
529 mLastCalcWidth = width;
531 NS_ASSERTION((mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN) ==
532 (mPrefWidth == NS_INTRINSIC_WIDTH_UNKNOWN),
533 "dirtyness out of sync");
534 NS_ASSERTION((mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN) ==
535 (mPrefWidthPctExpand == NS_INTRINSIC_WIDTH_UNKNOWN),
536 "dirtyness out of sync");
537 // XXX Is this needed?
538 if (mMinWidth == NS_INTRINSIC_WIDTH_UNKNOWN)
539 ComputeIntrinsicWidths(aReflowState.rendContext);
541 nsTableCellMap *cellMap = mTableFrame->GetCellMap();
542 int32_t colCount = cellMap->GetColCount();
543 if (colCount <= 0)
544 return; // nothing to do
546 DistributeWidthToColumns(width, 0, colCount, BTLS_FINAL_WIDTH, false);
548 #ifdef DEBUG_TABLE_STRATEGY
549 printf("ComputeColumnWidths final\n");
550 mTableFrame->Dump(false, true, false);
551 #endif
552 }
554 void
555 BasicTableLayoutStrategy::DistributePctWidthToColumns(float aSpanPrefPct,
556 int32_t aFirstCol,
557 int32_t aColCount)
558 {
559 // First loop to determine:
560 int32_t nonPctColCount = 0; // number of spanned columns without % width
561 nscoord nonPctTotalPrefWidth = 0; // total pref width of those columns
562 // and to reduce aSpanPrefPct by columns that already have % width
564 int32_t scol, scol_end;
565 nsTableCellMap *cellMap = mTableFrame->GetCellMap();
566 for (scol = aFirstCol, scol_end = aFirstCol + aColCount;
567 scol < scol_end; ++scol) {
568 nsTableColFrame *scolFrame = mTableFrame->GetColFrame(scol);
569 if (!scolFrame) {
570 NS_ERROR("column frames out of sync with cell map");
571 continue;
572 }
573 float scolPct = scolFrame->GetPrefPercent();
574 if (scolPct == 0.0f) {
575 nonPctTotalPrefWidth += scolFrame->GetPrefCoord();
576 if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) {
577 ++nonPctColCount;
578 }
579 } else {
580 aSpanPrefPct -= scolPct;
581 }
582 }
584 if (aSpanPrefPct <= 0.0f || nonPctColCount == 0) {
585 // There's no %-width on the colspan left over to distribute,
586 // or there are no columns to which we could distribute %-width
587 return;
588 }
590 // Second loop, to distribute what remains of aSpanPrefPct
591 // between the non-percent-width spanned columns
592 const bool spanHasNonPctPref = nonPctTotalPrefWidth > 0; // Loop invariant
593 for (scol = aFirstCol, scol_end = aFirstCol + aColCount;
594 scol < scol_end; ++scol) {
595 nsTableColFrame *scolFrame = mTableFrame->GetColFrame(scol);
596 if (!scolFrame) {
597 NS_ERROR("column frames out of sync with cell map");
598 continue;
599 }
601 if (scolFrame->GetPrefPercent() == 0.0f) {
602 NS_ASSERTION((!spanHasNonPctPref ||
603 nonPctTotalPrefWidth != 0) &&
604 nonPctColCount != 0,
605 "should not be zero if we haven't allocated "
606 "all pref percent");
608 float allocatedPct; // % width to be given to this column
609 if (spanHasNonPctPref) {
610 // Group so we're multiplying by 1.0f when we need
611 // to use up aSpanPrefPct.
612 allocatedPct = aSpanPrefPct *
613 (float(scolFrame->GetPrefCoord()) /
614 float(nonPctTotalPrefWidth));
615 } else if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) {
616 // distribute equally when all pref widths are 0
617 allocatedPct = aSpanPrefPct / float(nonPctColCount);
618 } else {
619 allocatedPct = 0.0f;
620 }
621 // Allocate the percent
622 scolFrame->AddSpanPrefPercent(allocatedPct);
624 // To avoid accumulating rounding error from division,
625 // subtract this column's values from the totals.
626 aSpanPrefPct -= allocatedPct;
627 nonPctTotalPrefWidth -= scolFrame->GetPrefCoord();
628 if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) {
629 --nonPctColCount;
630 }
632 if (!aSpanPrefPct) {
633 // No more span-percent-width to distribute --> we're done.
634 NS_ASSERTION(spanHasNonPctPref ?
635 nonPctTotalPrefWidth == 0 :
636 nonPctColCount == 0,
637 "No more pct width to distribute, but there are "
638 "still cols that need some.");
639 return;
640 }
641 }
642 }
643 }
645 void
646 BasicTableLayoutStrategy::DistributeWidthToColumns(nscoord aWidth,
647 int32_t aFirstCol,
648 int32_t aColCount,
649 BtlsWidthType aWidthType,
650 bool aSpanHasSpecifiedWidth)
651 {
652 NS_ASSERTION(aWidthType != BTLS_FINAL_WIDTH ||
653 (aFirstCol == 0 &&
654 aColCount == mTableFrame->GetCellMap()->GetColCount()),
655 "Computing final column widths, but didn't get full column range");
657 // border-spacing isn't part of the basis for percentages.
658 nscoord spacing = mTableFrame->GetCellSpacingX();
659 nscoord subtract = 0;
660 // aWidth initially includes border-spacing for the boundaries in between
661 // each of the columns. We start at aFirstCol + 1 because the first
662 // in-between boundary would be at the left edge of column aFirstCol + 1
663 for (int32_t col = aFirstCol + 1; col < aFirstCol + aColCount; ++col) {
664 if (mTableFrame->ColumnHasCellSpacingBefore(col)) {
665 subtract += spacing;
666 }
667 }
668 if (aWidthType == BTLS_FINAL_WIDTH) {
669 // If we're computing final col-width, then aWidth initially includes
670 // border spacing on the table's far left + far right edge, too. Need
671 // to subtract those out, too.
672 subtract += spacing * 2;
673 }
674 aWidth = NSCoordSaturatingSubtract(aWidth, subtract, nscoord_MAX);
676 /*
677 * The goal of this function is to distribute |aWidth| between the
678 * columns by making an appropriate AddSpanCoords or SetFinalWidth
679 * call for each column. (We call AddSpanCoords if we're
680 * distributing a column-spanning cell's minimum or preferred width
681 * to its spanned columns. We call SetFinalWidth if we're
682 * distributing a table's final width to its columns.)
683 *
684 * The idea is to either assign one of the following sets of widths
685 * or a weighted average of two adjacent sets of widths. It is not
686 * possible to assign values smaller than the smallest set of
687 * widths. However, see below for handling the case of assigning
688 * values larger than the largest set of widths. From smallest to
689 * largest, these are:
690 *
691 * 1. [guess_min] Assign all columns their min width.
692 *
693 * 2. [guess_min_pct] Assign all columns with percentage widths
694 * their percentage width, and all other columns their min width.
695 *
696 * 3. [guess_min_spec] Assign all columns with percentage widths
697 * their percentage width, all columns with specified coordinate
698 * widths their pref width (since it doesn't matter whether it's the
699 * largest contributor to the pref width that was the specified
700 * contributor), and all other columns their min width.
701 *
702 * 4. [guess_pref] Assign all columns with percentage widths their
703 * specified width, and all other columns their pref width.
704 *
705 * If |aWidth| is *larger* than what we would assign in (4), then we
706 * expand the columns:
707 *
708 * a. if any columns without a specified coordinate width or
709 * percent width have nonzero pref width, in proportion to pref
710 * width [total_flex_pref]
711 *
712 * b. otherwise, if any columns without a specified coordinate
713 * width or percent width, but with cells originating in them,
714 * have zero pref width, equally between these
715 * [numNonSpecZeroWidthCols]
716 *
717 * c. otherwise, if any columns without percent width have nonzero
718 * pref width, in proportion to pref width [total_fixed_pref]
719 *
720 * d. otherwise, if any columns have nonzero percentage widths, in
721 * proportion to the percentage widths [total_pct]
722 *
723 * e. otherwise, equally.
724 */
726 // Loop #1 over the columns, to figure out the four values above so
727 // we know which case we're dealing with.
729 nscoord guess_min = 0,
730 guess_min_pct = 0,
731 guess_min_spec = 0,
732 guess_pref = 0,
733 total_flex_pref = 0,
734 total_fixed_pref = 0;
735 float total_pct = 0.0f; // 0.0f to 1.0f
736 int32_t numInfiniteWidthCols = 0;
737 int32_t numNonSpecZeroWidthCols = 0;
739 int32_t col;
740 nsTableCellMap *cellMap = mTableFrame->GetCellMap();
741 for (col = aFirstCol; col < aFirstCol + aColCount; ++col) {
742 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
743 if (!colFrame) {
744 NS_ERROR("column frames out of sync with cell map");
745 continue;
746 }
747 nscoord min_width = colFrame->GetMinCoord();
748 guess_min += min_width;
749 if (colFrame->GetPrefPercent() != 0.0f) {
750 float pct = colFrame->GetPrefPercent();
751 total_pct += pct;
752 nscoord val = nscoord(float(aWidth) * pct);
753 if (val < min_width)
754 val = min_width;
755 guess_min_pct += val;
756 guess_pref = NSCoordSaturatingAdd(guess_pref, val);
757 } else {
758 nscoord pref_width = colFrame->GetPrefCoord();
759 if (pref_width == nscoord_MAX) {
760 ++numInfiniteWidthCols;
761 }
762 guess_pref = NSCoordSaturatingAdd(guess_pref, pref_width);
763 guess_min_pct += min_width;
764 if (colFrame->GetHasSpecifiedCoord()) {
765 // we'll add on the rest of guess_min_spec outside the
766 // loop
767 nscoord delta = NSCoordSaturatingSubtract(pref_width,
768 min_width, 0);
769 guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, delta);
770 total_fixed_pref = NSCoordSaturatingAdd(total_fixed_pref,
771 pref_width);
772 } else if (pref_width == 0) {
773 if (cellMap->GetNumCellsOriginatingInCol(col) > 0) {
774 ++numNonSpecZeroWidthCols;
775 }
776 } else {
777 total_flex_pref = NSCoordSaturatingAdd(total_flex_pref,
778 pref_width);
779 }
780 }
781 }
782 guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, guess_min_pct);
784 // Determine what we're flexing:
785 enum Loop2Type {
786 FLEX_PCT_SMALL, // between (1) and (2) above
787 FLEX_FIXED_SMALL, // between (2) and (3) above
788 FLEX_FLEX_SMALL, // between (3) and (4) above
789 FLEX_FLEX_LARGE, // greater than (4) above, case (a)
790 FLEX_FLEX_LARGE_ZERO, // greater than (4) above, case (b)
791 FLEX_FIXED_LARGE, // greater than (4) above, case (c)
792 FLEX_PCT_LARGE, // greater than (4) above, case (d)
793 FLEX_ALL_LARGE // greater than (4) above, case (e)
794 };
796 Loop2Type l2t;
797 // These are constants (over columns) for each case's math. We use
798 // a pair of nscoords rather than a float so that we can subtract
799 // each column's allocation so we avoid accumulating rounding error.
800 nscoord space; // the amount of extra width to allocate
801 union {
802 nscoord c;
803 float f;
804 } basis; // the sum of the statistic over columns to divide it
805 if (aWidth < guess_pref) {
806 if (aWidthType != BTLS_FINAL_WIDTH && aWidth <= guess_min) {
807 // Return early -- we don't have any extra space to distribute.
808 return;
809 }
810 NS_ASSERTION(!(aWidthType == BTLS_FINAL_WIDTH && aWidth < guess_min),
811 "Table width is less than the "
812 "sum of its columns' min widths");
813 if (aWidth < guess_min_pct) {
814 l2t = FLEX_PCT_SMALL;
815 space = aWidth - guess_min;
816 basis.c = guess_min_pct - guess_min;
817 } else if (aWidth < guess_min_spec) {
818 l2t = FLEX_FIXED_SMALL;
819 space = aWidth - guess_min_pct;
820 basis.c = NSCoordSaturatingSubtract(guess_min_spec, guess_min_pct,
821 nscoord_MAX);
822 } else {
823 l2t = FLEX_FLEX_SMALL;
824 space = aWidth - guess_min_spec;
825 basis.c = NSCoordSaturatingSubtract(guess_pref, guess_min_spec,
826 nscoord_MAX);
827 }
828 } else {
829 space = NSCoordSaturatingSubtract(aWidth, guess_pref, nscoord_MAX);
830 if (total_flex_pref > 0) {
831 l2t = FLEX_FLEX_LARGE;
832 basis.c = total_flex_pref;
833 } else if (numNonSpecZeroWidthCols > 0) {
834 l2t = FLEX_FLEX_LARGE_ZERO;
835 basis.c = numNonSpecZeroWidthCols;
836 } else if (total_fixed_pref > 0) {
837 l2t = FLEX_FIXED_LARGE;
838 basis.c = total_fixed_pref;
839 } else if (total_pct > 0.0f) {
840 l2t = FLEX_PCT_LARGE;
841 basis.f = total_pct;
842 } else {
843 l2t = FLEX_ALL_LARGE;
844 basis.c = aColCount;
845 }
846 }
848 #ifdef DEBUG_dbaron_off
849 printf("ComputeColumnWidths: %d columns in width %d,\n"
850 " guesses=[%d,%d,%d,%d], totals=[%d,%d,%f],\n"
851 " l2t=%d, space=%d, basis.c=%d\n",
852 aColCount, aWidth,
853 guess_min, guess_min_pct, guess_min_spec, guess_pref,
854 total_flex_pref, total_fixed_pref, total_pct,
855 l2t, space, basis.c);
856 #endif
858 for (col = aFirstCol; col < aFirstCol + aColCount; ++col) {
859 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
860 if (!colFrame) {
861 NS_ERROR("column frames out of sync with cell map");
862 continue;
863 }
864 nscoord col_width;
866 float pct = colFrame->GetPrefPercent();
867 if (pct != 0.0f) {
868 col_width = nscoord(float(aWidth) * pct);
869 nscoord col_min = colFrame->GetMinCoord();
870 if (col_width < col_min)
871 col_width = col_min;
872 } else {
873 col_width = colFrame->GetPrefCoord();
874 }
876 nscoord col_width_before_adjust = col_width;
878 switch (l2t) {
879 case FLEX_PCT_SMALL:
880 col_width = col_width_before_adjust = colFrame->GetMinCoord();
881 if (pct != 0.0f) {
882 nscoord pct_minus_min =
883 nscoord(float(aWidth) * pct) - col_width;
884 if (pct_minus_min > 0) {
885 float c = float(space) / float(basis.c);
886 basis.c -= pct_minus_min;
887 col_width += NSToCoordRound(float(pct_minus_min) * c);
888 }
889 }
890 break;
891 case FLEX_FIXED_SMALL:
892 if (pct == 0.0f) {
893 NS_ASSERTION(col_width == colFrame->GetPrefCoord(),
894 "wrong width assigned");
895 if (colFrame->GetHasSpecifiedCoord()) {
896 nscoord col_min = colFrame->GetMinCoord();
897 nscoord pref_minus_min = col_width - col_min;
898 col_width = col_width_before_adjust = col_min;
899 if (pref_minus_min != 0) {
900 float c = float(space) / float(basis.c);
901 basis.c -= pref_minus_min;
902 col_width += NSToCoordRound(
903 float(pref_minus_min) * c);
904 }
905 } else
906 col_width = col_width_before_adjust =
907 colFrame->GetMinCoord();
908 }
909 break;
910 case FLEX_FLEX_SMALL:
911 if (pct == 0.0f &&
912 !colFrame->GetHasSpecifiedCoord()) {
913 NS_ASSERTION(col_width == colFrame->GetPrefCoord(),
914 "wrong width assigned");
915 nscoord col_min = colFrame->GetMinCoord();
916 nscoord pref_minus_min =
917 NSCoordSaturatingSubtract(col_width, col_min, 0);
918 col_width = col_width_before_adjust = col_min;
919 if (pref_minus_min != 0) {
920 float c = float(space) / float(basis.c);
921 // If we have infinite-width cols, then the standard
922 // adjustment to col_width using 'c' won't work,
923 // because basis.c and pref_minus_min are both
924 // nscoord_MAX and will cancel each other out in the
925 // col_width adjustment (making us assign all the
926 // space to the first inf-width col). To correct for
927 // this, we'll also divide by numInfiniteWidthCols to
928 // spread the space equally among the inf-width cols.
929 if (numInfiniteWidthCols) {
930 if (colFrame->GetPrefCoord() == nscoord_MAX) {
931 c = c / float(numInfiniteWidthCols);
932 --numInfiniteWidthCols;
933 } else {
934 c = 0.0f;
935 }
936 }
937 basis.c = NSCoordSaturatingSubtract(basis.c,
938 pref_minus_min,
939 nscoord_MAX);
940 col_width += NSToCoordRound(
941 float(pref_minus_min) * c);
942 }
943 }
944 break;
945 case FLEX_FLEX_LARGE:
946 if (pct == 0.0f &&
947 !colFrame->GetHasSpecifiedCoord()) {
948 NS_ASSERTION(col_width == colFrame->GetPrefCoord(),
949 "wrong width assigned");
950 if (col_width != 0) {
951 if (space == nscoord_MAX) {
952 basis.c -= col_width;
953 col_width = nscoord_MAX;
954 } else {
955 float c = float(space) / float(basis.c);
956 basis.c -= col_width;
957 col_width += NSToCoordRound(float(col_width) * c);
958 }
959 }
960 }
961 break;
962 case FLEX_FLEX_LARGE_ZERO:
963 if (pct == 0.0f &&
964 !colFrame->GetHasSpecifiedCoord() &&
965 cellMap->GetNumCellsOriginatingInCol(col) > 0) {
967 NS_ASSERTION(col_width == 0 &&
968 colFrame->GetPrefCoord() == 0,
969 "Since we're in FLEX_FLEX_LARGE_ZERO case, "
970 "all auto-width cols should have zero pref "
971 "width.");
972 float c = float(space) / float(basis.c);
973 col_width += NSToCoordRound(c);
974 --basis.c;
975 }
976 break;
977 case FLEX_FIXED_LARGE:
978 if (pct == 0.0f) {
979 NS_ASSERTION(col_width == colFrame->GetPrefCoord(),
980 "wrong width assigned");
981 NS_ASSERTION(colFrame->GetHasSpecifiedCoord() ||
982 colFrame->GetPrefCoord() == 0,
983 "wrong case");
984 if (col_width != 0) {
985 float c = float(space) / float(basis.c);
986 basis.c -= col_width;
987 col_width += NSToCoordRound(float(col_width) * c);
988 }
989 }
990 break;
991 case FLEX_PCT_LARGE:
992 NS_ASSERTION(pct != 0.0f || colFrame->GetPrefCoord() == 0,
993 "wrong case");
994 if (pct != 0.0f) {
995 float c = float(space) / basis.f;
996 col_width += NSToCoordRound(pct * c);
997 basis.f -= pct;
998 }
999 break;
1000 case FLEX_ALL_LARGE:
1001 {
1002 float c = float(space) / float(basis.c);
1003 col_width += NSToCoordRound(c);
1004 --basis.c;
1005 }
1006 break;
1007 }
1009 // Only subtract from space if it's a real number.
1010 if (space != nscoord_MAX) {
1011 NS_ASSERTION(col_width != nscoord_MAX,
1012 "How is col_width nscoord_MAX if space isn't?");
1013 NS_ASSERTION(col_width_before_adjust != nscoord_MAX,
1014 "How is col_width_before_adjust nscoord_MAX if space isn't?");
1015 space -= col_width - col_width_before_adjust;
1016 }
1018 NS_ASSERTION(col_width >= colFrame->GetMinCoord(),
1019 "assigned width smaller than min");
1021 // Apply the new width
1022 switch (aWidthType) {
1023 case BTLS_MIN_WIDTH:
1024 {
1025 // Note: AddSpanCoords requires both a min and pref width.
1026 // For the pref width, we'll just pass in our computed
1027 // min width, because the real pref width will be at least
1028 // as big
1029 colFrame->AddSpanCoords(col_width, col_width,
1030 aSpanHasSpecifiedWidth);
1031 }
1032 break;
1033 case BTLS_PREF_WIDTH:
1034 {
1035 // Note: AddSpanCoords requires both a min and pref width.
1036 // For the min width, we'll just pass in 0, because
1037 // the real min width will be at least 0
1038 colFrame->AddSpanCoords(0, col_width,
1039 aSpanHasSpecifiedWidth);
1040 }
1041 break;
1042 case BTLS_FINAL_WIDTH:
1043 {
1044 nscoord old_final = colFrame->GetFinalWidth();
1045 colFrame->SetFinalWidth(col_width);
1047 if (old_final != col_width)
1048 mTableFrame->DidResizeColumns();
1049 }
1050 break;
1051 }
1052 }
1053 NS_ASSERTION((space == 0 || space == nscoord_MAX) &&
1054 ((l2t == FLEX_PCT_LARGE)
1055 ? (-0.001f < basis.f && basis.f < 0.001f)
1056 : (basis.c == 0 || basis.c == nscoord_MAX)),
1057 "didn't subtract all that we added");
1058 }