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/. */
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"
18 FixedTableLayoutStrategy::FixedTableLayoutStrategy(nsTableFrame
*aTableFrame
)
19 : nsITableLayoutStrategy(nsITableLayoutStrategy::Fixed
)
20 , mTableFrame(aTableFrame
)
22 MarkIntrinsicWidthsDirty();
26 FixedTableLayoutStrategy::~FixedTableLayoutStrategy()
31 FixedTableLayoutStrategy::GetMinWidth(nsRenderingContext
* aRenderingContext
)
33 DISPLAY_MIN_WIDTH(mTableFrame
, mMinWidth
);
34 if (mMinWidth
!= NS_INTRINSIC_WIDTH_UNKNOWN
)
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();
59 result
+= spacing
* (colCount
+ 1);
62 for (int32_t col
= 0; col
< colCount
; ++col
) {
63 nsTableColFrame
*colFrame
= mTableFrame
->GetColFrame(col
);
65 NS_ERROR("column frames out of sync with cell map");
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
) {
76 NS_ASSERTION(styleWidth
->GetUnit() == eStyleUnit_Auto
||
77 styleWidth
->GetUnit() == eStyleUnit_Enumerated
||
78 (styleWidth
->IsCalcUnit() && styleWidth
->CalcHasPercent()),
81 // The 'table-layout: fixed' algorithm considers only cells
85 nsTableCellFrame
*cellFrame
=
86 cellMap
->GetCellInfoAt(0, col
, &originates
, &colSpan
);
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
);
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
;
103 } else if (styleWidth
->GetUnit() == eStyleUnit_Percent
) {
105 // XXX Can this force columns to negative
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
,
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
129 nscoord result
= nscoord_MAX
;
130 DISPLAY_PREF_WIDTH(mTableFrame
, result
);
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
);
154 FixedTableLayoutStrategy::ComputeColumnWidths(const nsHTMLReflowState
& aReflowState
)
156 nscoord tableWidth
= aReflowState
.ComputedWidth();
158 if (mLastCalcWidth
== tableWidth
)
160 mLastCalcWidth
= tableWidth
;
162 nsTableCellMap
*cellMap
= mTableFrame
->GetCellMap();
163 int32_t colCount
= cellMap
->GetColCount();
164 nscoord spacing
= mTableFrame
->GetCellSpacingX();
167 // No Columns - nothing to compute
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
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
);
203 oldColWidths
.AppendElement(0);
204 NS_ERROR("column frames out of sync with cell map");
207 oldColWidths
.AppendElement(colFrame
->GetFinalWidth());
208 colFrame
->ResetPrefPercent();
209 const nsStyleCoord
*styleWidth
=
210 &colFrame
->StylePosition()->mWidth
;
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
);
223 NS_ASSERTION(styleWidth
->GetUnit() == eStyleUnit_Auto
||
224 styleWidth
->GetUnit() == eStyleUnit_Enumerated
||
225 (styleWidth
->IsCalcUnit() && styleWidth
->CalcHasPercent()),
228 // The 'table-layout: fixed' algorithm considers only cells
232 nsTableCellFrame
*cellFrame
=
233 cellMap
->GetCellInfoAt(0, col
, &originates
, &colSpan
);
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
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
;
261 case NS_STYLE_BOX_SIZING_PADDING
:
262 boxSizingAdjust
+= offsets
.hBorder
;
264 case NS_STYLE_BOX_SIZING_BORDER
:
265 // Don't add anything
268 colWidth
+= boxSizingAdjust
;
270 pct
/= float(colSpan
);
271 colFrame
->AddPrefPercent(pct
);
274 // 'auto', '-moz-available', '-moz-fit-content', and
275 // 'calc()' with percentages
276 colWidth
= unassignedMarker
;
278 if (colWidth
!= unassignedMarker
) {
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
;
288 if (styleWidth
->GetUnit() != eStyleUnit_Percent
) {
289 specTotal
+= colWidth
;
293 colWidth
= unassignedMarker
;
297 colFrame
->SetFinalWidth(colWidth
);
299 if (colWidth
== unassignedMarker
) {
302 unassignedSpace
-= colWidth
;
306 if (unassignedSpace
< 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
);
317 NS_ERROR("column frames out of sync with cell map");
320 nscoord colWidth
= colFrame
->GetFinalWidth();
321 colWidth
-= NSToCoordFloor(colFrame
->GetPrefPercent() *
325 colFrame
->SetFinalWidth(colWidth
);
331 if (unassignedCount
> 0) {
332 // The spec says to distribute the remaining space evenly among
334 nscoord toAssign
= unassignedSpace
/ unassignedCount
;
335 for (int32_t col
= 0; col
< colCount
; ++col
) {
336 nsTableColFrame
*colFrame
= mTableFrame
->GetColFrame(col
);
338 NS_ERROR("column frames out of sync with cell map");
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.
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
);
352 NS_ERROR("column frames out of sync with cell map");
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");
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
);
377 NS_ERROR("column frames out of sync with cell map");
380 if (pctUndist
< colFrame
->GetPrefPercent()) {
381 // This can happen with floating-point math.
382 NS_ASSERTION(colFrame
->GetPrefPercent() - pctUndist
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
) {
396 NS_ASSERTION(unassignedSpace
== 0, "failed to redistribute");
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
);
403 NS_ERROR("column frames out of sync with cell map");
406 NS_ASSERTION(colFrame
->GetFinalWidth() == 0, "yikes");
407 nscoord toAdd
= AllocateUnassigned(unassignedSpace
,
408 1.0f
/ float(colsLeft
));
409 colFrame
->SetFinalWidth(toAdd
);
410 unassignedSpace
-= toAdd
;
413 NS_ASSERTION(unassignedSpace
== 0, "failed to redistribute");
416 for (int32_t col
= 0; col
< colCount
; ++col
) {
417 nsTableColFrame
*colFrame
= mTableFrame
->GetColFrame(col
);
419 NS_ERROR("column frames out of sync with cell map");
422 if (oldColWidths
.ElementAt(col
) != colFrame
->GetFinalWidth()) {
423 mTableFrame
->DidResizeColumns();