Backout a74bd5095902, Bug 959405 - Please update the Buri Moz-central, 1.3, 1.2 with...
[gecko.git] / layout / tables / FixedTableLayoutStrategy.cpp
blobad686f15231fb695efea5c9658181d1708ae4536
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 * Algorithms that determine column and table widths used for CSS2's
9 * 'table-layout: fixed'.
12 #include "FixedTableLayoutStrategy.h"
13 #include "nsTableFrame.h"
14 #include "nsTableColFrame.h"
15 #include "nsTableCellFrame.h"
16 #include <algorithm>
18 FixedTableLayoutStrategy::FixedTableLayoutStrategy(nsTableFrame *aTableFrame)
19 : nsITableLayoutStrategy(nsITableLayoutStrategy::Fixed)
20 , mTableFrame(aTableFrame)
22 MarkIntrinsicWidthsDirty();
25 /* virtual */
26 FixedTableLayoutStrategy::~FixedTableLayoutStrategy()
30 /* virtual */ nscoord
31 FixedTableLayoutStrategy::GetMinWidth(nsRenderingContext* aRenderingContext)
33 DISPLAY_MIN_WIDTH(mTableFrame, mMinWidth);
34 if (mMinWidth != NS_INTRINSIC_WIDTH_UNKNOWN)
35 return mMinWidth;
37 // It's theoretically possible to do something much better here that
38 // depends only on the columns and the first row (where we look at
39 // intrinsic widths inside the first row and then reverse the
40 // algorithm to find the narrowest width that would hold all of
41 // those intrinsic widths), but it wouldn't be compatible with other
42 // browsers, or with the use of GetMinWidth by
43 // nsTableFrame::ComputeSize to determine the width of a fixed
44 // layout table, since CSS2.1 says:
45 // The width of the table is then the greater of the value of the
46 // 'width' property for the table element and the sum of the
47 // column widths (plus cell spacing or borders).
49 // XXX Should we really ignore 'min-width' and 'max-width'?
50 // XXX Should we really ignore widths on column groups?
52 nsTableCellMap *cellMap = mTableFrame->GetCellMap();
53 int32_t colCount = cellMap->GetColCount();
54 nscoord spacing = mTableFrame->GetCellSpacingX();
56 nscoord result = 0;
58 if (colCount > 0) {
59 result += spacing * (colCount + 1);
62 for (int32_t col = 0; col < colCount; ++col) {
63 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
64 if (!colFrame) {
65 NS_ERROR("column frames out of sync with cell map");
66 continue;
68 const nsStyleCoord *styleWidth =
69 &colFrame->StylePosition()->mWidth;
70 if (styleWidth->ConvertsToLength()) {
71 result += nsLayoutUtils::ComputeWidthValue(aRenderingContext,
72 colFrame, 0, 0, 0, *styleWidth);
73 } else if (styleWidth->GetUnit() == eStyleUnit_Percent) {
74 // do nothing
75 } else {
76 NS_ASSERTION(styleWidth->GetUnit() == eStyleUnit_Auto ||
77 styleWidth->GetUnit() == eStyleUnit_Enumerated ||
78 (styleWidth->IsCalcUnit() && styleWidth->CalcHasPercent()),
79 "bad width");
81 // The 'table-layout: fixed' algorithm considers only cells
82 // in the first row.
83 bool originates;
84 int32_t colSpan;
85 nsTableCellFrame *cellFrame =
86 cellMap->GetCellInfoAt(0, col, &originates, &colSpan);
87 if (cellFrame) {
88 styleWidth = &cellFrame->StylePosition()->mWidth;
89 if (styleWidth->ConvertsToLength() ||
90 (styleWidth->GetUnit() == eStyleUnit_Enumerated &&
91 (styleWidth->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT ||
92 styleWidth->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT))) {
93 nscoord cellWidth = nsLayoutUtils::IntrinsicForContainer(
94 aRenderingContext, cellFrame, nsLayoutUtils::MIN_WIDTH);
95 if (colSpan > 1) {
96 // If a column-spanning cell is in the first
97 // row, split up the space evenly. (XXX This
98 // isn't quite right if some of the columns it's
99 // in have specified widths. Should we care?)
100 cellWidth = ((cellWidth + spacing) / colSpan) - spacing;
102 result += cellWidth;
103 } else if (styleWidth->GetUnit() == eStyleUnit_Percent) {
104 if (colSpan > 1) {
105 // XXX Can this force columns to negative
106 // widths?
107 result -= spacing * (colSpan - 1);
110 // else, for 'auto', '-moz-available', '-moz-fit-content',
111 // and 'calc()' with percentages, do nothing
116 return (mMinWidth = result);
119 /* virtual */ nscoord
120 FixedTableLayoutStrategy::GetPrefWidth(nsRenderingContext* aRenderingContext,
121 bool aComputingSize)
123 // It's theoretically possible to do something much better here that
124 // depends only on the columns and the first row (where we look at
125 // intrinsic widths inside the first row and then reverse the
126 // algorithm to find the narrowest width that would hold all of
127 // those intrinsic widths), but it wouldn't be compatible with other
128 // browsers.
129 nscoord result = nscoord_MAX;
130 DISPLAY_PREF_WIDTH(mTableFrame, result);
131 return result;
134 /* virtual */ void
135 FixedTableLayoutStrategy::MarkIntrinsicWidthsDirty()
137 mMinWidth = NS_INTRINSIC_WIDTH_UNKNOWN;
138 mLastCalcWidth = nscoord_MIN;
141 static inline nscoord
142 AllocateUnassigned(nscoord aUnassignedSpace, float aShare)
144 if (aShare == 1.0f) {
145 // This happens when the numbers we're dividing to get aShare
146 // are equal. We want to return unassignedSpace exactly, even
147 // if it can't be precisely round-tripped through float.
148 return aUnassignedSpace;
150 return NSToCoordRound(float(aUnassignedSpace) * aShare);
153 /* virtual */ void
154 FixedTableLayoutStrategy::ComputeColumnWidths(const nsHTMLReflowState& aReflowState)
156 nscoord tableWidth = aReflowState.ComputedWidth();
158 if (mLastCalcWidth == tableWidth)
159 return;
160 mLastCalcWidth = tableWidth;
162 nsTableCellMap *cellMap = mTableFrame->GetCellMap();
163 int32_t colCount = cellMap->GetColCount();
164 nscoord spacing = mTableFrame->GetCellSpacingX();
166 if (colCount == 0) {
167 // No Columns - nothing to compute
168 return;
171 // border-spacing isn't part of the basis for percentages.
172 tableWidth -= spacing * (colCount + 1);
174 // store the old column widths. We might call multiple times SetFinalWidth
175 // on the columns, due to this we can't compare at the last call that the
176 // width has changed with the respect to the last call to
177 // ComputeColumnWidths. In order to overcome this we store the old values
178 // in this array. A single call to SetFinalWidth would make it possible to
179 // call GetFinalWidth before and to compare when setting the final width.
180 nsTArray<nscoord> oldColWidths;
182 // XXX This ignores the 'min-width' and 'max-width' properties
183 // throughout. Then again, that's what the CSS spec says to do.
185 // XXX Should we really ignore widths on column groups?
187 uint32_t unassignedCount = 0;
188 nscoord unassignedSpace = tableWidth;
189 const nscoord unassignedMarker = nscoord_MIN;
191 // We use the PrefPercent on the columns to store the percentages
192 // used to compute column widths in case we need to shrink or expand
193 // the columns.
194 float pctTotal = 0.0f;
196 // Accumulate the total specified (non-percent) on the columns for
197 // distributing excess width to the columns.
198 nscoord specTotal = 0;
200 for (int32_t col = 0; col < colCount; ++col) {
201 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
202 if (!colFrame) {
203 oldColWidths.AppendElement(0);
204 NS_ERROR("column frames out of sync with cell map");
205 continue;
207 oldColWidths.AppendElement(colFrame->GetFinalWidth());
208 colFrame->ResetPrefPercent();
209 const nsStyleCoord *styleWidth =
210 &colFrame->StylePosition()->mWidth;
211 nscoord colWidth;
212 if (styleWidth->ConvertsToLength()) {
213 colWidth = nsLayoutUtils::ComputeWidthValue(
214 aReflowState.rendContext,
215 colFrame, 0, 0, 0, *styleWidth);
216 specTotal += colWidth;
217 } else if (styleWidth->GetUnit() == eStyleUnit_Percent) {
218 float pct = styleWidth->GetPercentValue();
219 colWidth = NSToCoordFloor(pct * float(tableWidth));
220 colFrame->AddPrefPercent(pct);
221 pctTotal += pct;
222 } else {
223 NS_ASSERTION(styleWidth->GetUnit() == eStyleUnit_Auto ||
224 styleWidth->GetUnit() == eStyleUnit_Enumerated ||
225 (styleWidth->IsCalcUnit() && styleWidth->CalcHasPercent()),
226 "bad width");
228 // The 'table-layout: fixed' algorithm considers only cells
229 // in the first row.
230 bool originates;
231 int32_t colSpan;
232 nsTableCellFrame *cellFrame =
233 cellMap->GetCellInfoAt(0, col, &originates, &colSpan);
234 if (cellFrame) {
235 styleWidth = &cellFrame->StylePosition()->mWidth;
236 if (styleWidth->ConvertsToLength() ||
237 (styleWidth->GetUnit() == eStyleUnit_Enumerated &&
238 (styleWidth->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT ||
239 styleWidth->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT))) {
240 // XXX This should use real percentage padding
241 // Note that the difference between MIN_WIDTH and
242 // PREF_WIDTH shouldn't matter for any of these
243 // values of styleWidth; use MIN_WIDTH for symmetry
244 // with GetMinWidth above, just in case there is a
245 // difference.
246 colWidth = nsLayoutUtils::IntrinsicForContainer(
247 aReflowState.rendContext,
248 cellFrame, nsLayoutUtils::MIN_WIDTH);
249 } else if (styleWidth->GetUnit() == eStyleUnit_Percent) {
250 // XXX This should use real percentage padding
251 nsIFrame::IntrinsicWidthOffsetData offsets =
252 cellFrame->IntrinsicWidthOffsets(aReflowState.rendContext);
253 float pct = styleWidth->GetPercentValue();
254 colWidth = NSToCoordFloor(pct * float(tableWidth));
256 nscoord boxSizingAdjust = 0;
257 switch (cellFrame->StylePosition()->mBoxSizing) {
258 case NS_STYLE_BOX_SIZING_CONTENT:
259 boxSizingAdjust += offsets.hPadding;
260 // Fall through
261 case NS_STYLE_BOX_SIZING_PADDING:
262 boxSizingAdjust += offsets.hBorder;
263 // Fall through
264 case NS_STYLE_BOX_SIZING_BORDER:
265 // Don't add anything
266 break;
268 colWidth += boxSizingAdjust;
270 pct /= float(colSpan);
271 colFrame->AddPrefPercent(pct);
272 pctTotal += pct;
273 } else {
274 // 'auto', '-moz-available', '-moz-fit-content', and
275 // 'calc()' with percentages
276 colWidth = unassignedMarker;
278 if (colWidth != unassignedMarker) {
279 if (colSpan > 1) {
280 // If a column-spanning cell is in the first
281 // row, split up the space evenly. (XXX This
282 // isn't quite right if some of the columns it's
283 // in have specified widths. Should we care?)
284 colWidth = ((colWidth + spacing) / colSpan) - spacing;
285 if (colWidth < 0)
286 colWidth = 0;
288 if (styleWidth->GetUnit() != eStyleUnit_Percent) {
289 specTotal += colWidth;
292 } else {
293 colWidth = unassignedMarker;
297 colFrame->SetFinalWidth(colWidth);
299 if (colWidth == unassignedMarker) {
300 ++unassignedCount;
301 } else {
302 unassignedSpace -= colWidth;
306 if (unassignedSpace < 0) {
307 if (pctTotal > 0) {
308 // If the columns took up too much space, reduce those that
309 // had percentage widths. The spec doesn't say to do this,
310 // but we've always done it in the past, and so does WinIE6.
311 nscoord pctUsed = NSToCoordFloor(pctTotal * float(tableWidth));
312 nscoord reduce = std::min(pctUsed, -unassignedSpace);
313 float reduceRatio = float(reduce) / pctTotal;
314 for (int32_t col = 0; col < colCount; ++col) {
315 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
316 if (!colFrame) {
317 NS_ERROR("column frames out of sync with cell map");
318 continue;
320 nscoord colWidth = colFrame->GetFinalWidth();
321 colWidth -= NSToCoordFloor(colFrame->GetPrefPercent() *
322 reduceRatio);
323 if (colWidth < 0)
324 colWidth = 0;
325 colFrame->SetFinalWidth(colWidth);
328 unassignedSpace = 0;
331 if (unassignedCount > 0) {
332 // The spec says to distribute the remaining space evenly among
333 // the columns.
334 nscoord toAssign = unassignedSpace / unassignedCount;
335 for (int32_t col = 0; col < colCount; ++col) {
336 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
337 if (!colFrame) {
338 NS_ERROR("column frames out of sync with cell map");
339 continue;
341 if (colFrame->GetFinalWidth() == unassignedMarker)
342 colFrame->SetFinalWidth(toAssign);
344 } else if (unassignedSpace > 0) {
345 // The spec doesn't say how to distribute the unassigned space.
346 if (specTotal > 0) {
347 // Distribute proportionally to non-percentage columns.
348 nscoord specUndist = specTotal;
349 for (int32_t col = 0; col < colCount; ++col) {
350 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
351 if (!colFrame) {
352 NS_ERROR("column frames out of sync with cell map");
353 continue;
355 if (colFrame->GetPrefPercent() == 0.0f) {
356 NS_ASSERTION(colFrame->GetFinalWidth() <= specUndist,
357 "widths don't add up");
358 nscoord toAdd = AllocateUnassigned(unassignedSpace,
359 float(colFrame->GetFinalWidth()) / float(specUndist));
360 specUndist -= colFrame->GetFinalWidth();
361 colFrame->SetFinalWidth(colFrame->GetFinalWidth() + toAdd);
362 unassignedSpace -= toAdd;
363 if (specUndist <= 0) {
364 NS_ASSERTION(specUndist == 0,
365 "math should be exact");
366 break;
370 NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
371 } else if (pctTotal > 0) {
372 // Distribute proportionally to percentage columns.
373 float pctUndist = pctTotal;
374 for (int32_t col = 0; col < colCount; ++col) {
375 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
376 if (!colFrame) {
377 NS_ERROR("column frames out of sync with cell map");
378 continue;
380 if (pctUndist < colFrame->GetPrefPercent()) {
381 // This can happen with floating-point math.
382 NS_ASSERTION(colFrame->GetPrefPercent() - pctUndist
383 < 0.0001,
384 "widths don't add up");
385 pctUndist = colFrame->GetPrefPercent();
387 nscoord toAdd = AllocateUnassigned(unassignedSpace,
388 colFrame->GetPrefPercent() / pctUndist);
389 colFrame->SetFinalWidth(colFrame->GetFinalWidth() + toAdd);
390 unassignedSpace -= toAdd;
391 pctUndist -= colFrame->GetPrefPercent();
392 if (pctUndist <= 0.0f) {
393 break;
396 NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
397 } else {
398 // Distribute equally to the zero-width columns.
399 int32_t colsLeft = colCount;
400 for (int32_t col = 0; col < colCount; ++col) {
401 nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
402 if (!colFrame) {
403 NS_ERROR("column frames out of sync with cell map");
404 continue;
406 NS_ASSERTION(colFrame->GetFinalWidth() == 0, "yikes");
407 nscoord toAdd = AllocateUnassigned(unassignedSpace,
408 1.0f / float(colsLeft));
409 colFrame->SetFinalWidth(toAdd);
410 unassignedSpace -= toAdd;
411 --colsLeft;
413 NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
416 for (int32_t 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 (oldColWidths.ElementAt(col) != colFrame->GetFinalWidth()) {
423 mTableFrame->DidResizeColumns();
424 break;