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