1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 // vim:cindent:ts=4:et:sw=4:
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is Mozilla's table layout code.
18 * The Initial Developer of the Original Code is the Mozilla Foundation.
19 * Portions created by the Initial Developer are Copyright (C) 2006
20 * the Initial Developer. All Rights Reserved.
23 * L. David Baron <dbaron@dbaron.org> (original author)
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
40 * Algorithms that determine column and table widths used for CSS2's
41 * 'table-layout: fixed'.
44 #include "FixedTableLayoutStrategy.h"
45 #include "nsTableFrame.h"
46 #include "nsTableColFrame.h"
47 #include "nsTableCellFrame.h"
49 FixedTableLayoutStrategy::FixedTableLayoutStrategy(nsTableFrame
*aTableFrame
)
50 : nsITableLayoutStrategy(nsITableLayoutStrategy::Fixed
)
51 , mTableFrame(aTableFrame
)
53 MarkIntrinsicWidthsDirty();
57 FixedTableLayoutStrategy::~FixedTableLayoutStrategy()
62 FixedTableLayoutStrategy::GetMinWidth(nsIRenderingContext
* aRenderingContext
)
64 DISPLAY_MIN_WIDTH(mTableFrame
, mMinWidth
);
65 if (mMinWidth
!= NS_INTRINSIC_WIDTH_UNKNOWN
)
68 // It's theoretically possible to do something much better here that
69 // depends only on the columns and the first row (where we look at
70 // intrinsic widths inside the first row and then reverse the
71 // algorithm to find the narrowest width that would hold all of
72 // those intrinsic widths), but it wouldn't be compatible with other
73 // browsers, or with the use of GetMinWidth by
74 // nsTableFrame::ComputeSize to determine the width of a fixed
75 // layout table, since CSS2.1 says:
76 // The width of the table is then the greater of the value of the
77 // 'width' property for the table element and the sum of the
78 // column widths (plus cell spacing or borders).
80 // XXX Should we really ignore 'min-width' and 'max-width'?
81 // XXX Should we really ignore widths on column groups?
83 nsTableCellMap
*cellMap
= mTableFrame
->GetCellMap();
84 PRInt32 colCount
= cellMap
->GetColCount();
85 nscoord spacing
= mTableFrame
->GetCellSpacingX();
90 result
+= spacing
* (colCount
+ 1);
93 for (PRInt32 col
= 0; col
< colCount
; ++col
) {
94 nsTableColFrame
*colFrame
= mTableFrame
->GetColFrame(col
);
96 NS_ERROR("column frames out of sync with cell map");
99 const nsStyleCoord
*styleWidth
=
100 &colFrame
->GetStylePosition()->mWidth
;
101 if (styleWidth
->GetUnit() == eStyleUnit_Coord
) {
102 result
+= nsLayoutUtils::ComputeWidthValue(aRenderingContext
,
103 colFrame
, 0, 0, 0, *styleWidth
);
104 } else if (styleWidth
->GetUnit() == eStyleUnit_Percent
) {
107 NS_ASSERTION(styleWidth
->GetUnit() == eStyleUnit_Auto
||
108 styleWidth
->GetUnit() == eStyleUnit_Enumerated
,
111 // The 'table-layout: fixed' algorithm considers only cells
115 nsTableCellFrame
*cellFrame
=
116 cellMap
->GetCellInfoAt(0, col
, &originates
, &colSpan
);
118 styleWidth
= &cellFrame
->GetStylePosition()->mWidth
;
119 if (styleWidth
->GetUnit() == eStyleUnit_Coord
||
120 (styleWidth
->GetUnit() == eStyleUnit_Enumerated
&&
121 (styleWidth
->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT
||
122 styleWidth
->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT
))) {
123 nscoord cellWidth
= nsLayoutUtils::IntrinsicForContainer(
124 aRenderingContext
, cellFrame
, nsLayoutUtils::MIN_WIDTH
);
126 // If a column-spanning cell is in the first
127 // row, split up the space evenly. (XXX This
128 // isn't quite right if some of the columns it's
129 // in have specified widths. Should we care?)
130 cellWidth
= ((cellWidth
+ spacing
) / colSpan
) - spacing
;
133 } else if (styleWidth
->GetUnit() == eStyleUnit_Percent
) {
135 // XXX Can this force columns to negative
137 result
-= spacing
* (colSpan
- 1);
140 // else, for 'auto', '-moz-available', and '-moz-fit-content'
146 return (mMinWidth
= result
);
149 /* virtual */ nscoord
150 FixedTableLayoutStrategy::GetPrefWidth(nsIRenderingContext
* aRenderingContext
,
151 PRBool aComputingSize
)
153 // It's theoretically possible to do something much better here that
154 // depends only on the columns and the first row (where we look at
155 // intrinsic widths inside the first row and then reverse the
156 // algorithm to find the narrowest width that would hold all of
157 // those intrinsic widths), but it wouldn't be compatible with other
159 nscoord result
= nscoord_MAX
;
160 DISPLAY_PREF_WIDTH(mTableFrame
, result
);
165 FixedTableLayoutStrategy::MarkIntrinsicWidthsDirty()
167 mMinWidth
= NS_INTRINSIC_WIDTH_UNKNOWN
;
168 mLastCalcWidth
= nscoord_MIN
;
171 static inline nscoord
172 AllocateUnassigned(nscoord aUnassignedSpace
, float aShare
)
174 if (aShare
== 1.0f
) {
175 // This happens when the numbers we're dividing to get aShare
176 // are equal. We want to return unassignedSpace exactly, even
177 // if it can't be precisely round-tripped through float.
178 return aUnassignedSpace
;
180 return NSToCoordRound(float(aUnassignedSpace
) * aShare
);
184 FixedTableLayoutStrategy::ComputeColumnWidths(const nsHTMLReflowState
& aReflowState
)
186 nscoord tableWidth
= aReflowState
.ComputedWidth();
188 if (mLastCalcWidth
== tableWidth
)
190 mLastCalcWidth
= tableWidth
;
192 nsTableCellMap
*cellMap
= mTableFrame
->GetCellMap();
193 PRInt32 colCount
= cellMap
->GetColCount();
194 nscoord spacing
= mTableFrame
->GetCellSpacingX();
197 // No Columns - nothing to compute
201 // border-spacing isn't part of the basis for percentages.
202 tableWidth
-= spacing
* (colCount
+ 1);
204 // store the old column widths. We might call multiple times SetFinalWidth
205 // on the columns, due to this we can't compare at the last call that the
206 // width has changed with the respect to the last call to
207 // ComputeColumnWidths. In order to overcome this we store the old values
208 // in this array. A single call to SetFinalWidth would make it possible to
209 // call GetFinalWidth before and to compare when setting the final width.
210 nsTArray
<nscoord
> oldColWidths
;
212 // XXX This ignores the 'min-width' and 'max-width' properties
213 // throughout. Then again, that's what the CSS spec says to do.
215 // XXX Should we really ignore widths on column groups?
217 PRUint32 unassignedCount
= 0;
218 nscoord unassignedSpace
= tableWidth
;
219 const nscoord unassignedMarker
= nscoord_MIN
;
221 // We use the PrefPercent on the columns to store the percentages
222 // used to compute column widths in case we need to shrink or expand
224 float pctTotal
= 0.0f
;
226 // Accumulate the total specified (non-percent) on the columns for
227 // distributing excess width to the columns.
228 nscoord specTotal
= 0;
230 for (PRInt32 col
= 0; col
< colCount
; ++col
) {
231 nsTableColFrame
*colFrame
= mTableFrame
->GetColFrame(col
);
233 oldColWidths
.AppendElement(0);
234 NS_ERROR("column frames out of sync with cell map");
237 oldColWidths
.AppendElement(colFrame
->GetFinalWidth());
238 colFrame
->ResetPrefPercent();
239 const nsStyleCoord
*styleWidth
=
240 &colFrame
->GetStylePosition()->mWidth
;
242 if (styleWidth
->GetUnit() == eStyleUnit_Coord
) {
243 colWidth
= nsLayoutUtils::ComputeWidthValue(
244 aReflowState
.rendContext
,
245 colFrame
, 0, 0, 0, *styleWidth
);
246 specTotal
+= colWidth
;
247 } else if (styleWidth
->GetUnit() == eStyleUnit_Percent
) {
248 float pct
= styleWidth
->GetPercentValue();
249 colWidth
= NSToCoordFloor(pct
* float(tableWidth
));
250 colFrame
->AddPrefPercent(pct
);
253 NS_ASSERTION(styleWidth
->GetUnit() == eStyleUnit_Auto
||
254 styleWidth
->GetUnit() == eStyleUnit_Enumerated
,
257 // The 'table-layout: fixed' algorithm considers only cells
261 nsTableCellFrame
*cellFrame
=
262 cellMap
->GetCellInfoAt(0, col
, &originates
, &colSpan
);
264 styleWidth
= &cellFrame
->GetStylePosition()->mWidth
;
265 if (styleWidth
->GetUnit() == eStyleUnit_Coord
||
266 (styleWidth
->GetUnit() == eStyleUnit_Enumerated
&&
267 (styleWidth
->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT
||
268 styleWidth
->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT
))) {
269 // XXX This should use real percentage padding
270 // Note that the difference between MIN_WIDTH and
271 // PREF_WIDTH shouldn't matter for any of these
272 // values of styleWidth; use MIN_WIDTH for symmetry
273 // with GetMinWidth above, just in case there is a
275 colWidth
= nsLayoutUtils::IntrinsicForContainer(
276 aReflowState
.rendContext
,
277 cellFrame
, nsLayoutUtils::MIN_WIDTH
);
278 } else if (styleWidth
->GetUnit() == eStyleUnit_Percent
) {
279 // XXX This should use real percentage padding
280 nsIFrame::IntrinsicWidthOffsetData offsets
=
281 cellFrame
->IntrinsicWidthOffsets(aReflowState
.rendContext
);
282 float pct
= styleWidth
->GetPercentValue();
283 colWidth
= NSToCoordFloor(pct
* float(tableWidth
)) +
284 offsets
.hPadding
+ offsets
.hBorder
;
285 pct
/= float(colSpan
);
286 colFrame
->AddPrefPercent(pct
);
289 // 'auto', '-moz-available', and '-moz-fit-content'
290 colWidth
= unassignedMarker
;
292 if (colWidth
!= unassignedMarker
) {
294 // If a column-spanning cell is in the first
295 // row, split up the space evenly. (XXX This
296 // isn't quite right if some of the columns it's
297 // in have specified widths. Should we care?)
298 colWidth
= ((colWidth
+ spacing
) / colSpan
) - spacing
;
302 if (styleWidth
->GetUnit() != eStyleUnit_Percent
) {
303 specTotal
+= colWidth
;
307 colWidth
= unassignedMarker
;
311 colFrame
->SetFinalWidth(colWidth
);
313 if (colWidth
== unassignedMarker
) {
316 unassignedSpace
-= colWidth
;
320 if (unassignedSpace
< 0) {
322 // If the columns took up too much space, reduce those that
323 // had percentage widths. The spec doesn't say to do this,
324 // but we've always done it in the past, and so does WinIE6.
325 nscoord pctUsed
= NSToCoordFloor(pctTotal
* float(tableWidth
));
326 nscoord reduce
= PR_MIN(pctUsed
, -unassignedSpace
);
327 float reduceRatio
= float(reduce
) / pctTotal
;
328 for (PRInt32 col
= 0; col
< colCount
; ++col
) {
329 nsTableColFrame
*colFrame
= mTableFrame
->GetColFrame(col
);
331 NS_ERROR("column frames out of sync with cell map");
334 nscoord colWidth
= colFrame
->GetFinalWidth();
335 colWidth
-= NSToCoordFloor(colFrame
->GetPrefPercent() *
339 colFrame
->SetFinalWidth(colWidth
);
345 if (unassignedCount
> 0) {
346 // The spec says to distribute the remaining space evenly among
348 nscoord toAssign
= unassignedSpace
/ unassignedCount
;
349 for (PRInt32 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
->GetFinalWidth() == unassignedMarker
)
356 colFrame
->SetFinalWidth(toAssign
);
358 } else if (unassignedSpace
> 0) {
359 // The spec doesn't say how to distribute the unassigned space.
361 // Distribute proportionally to non-percentage columns.
362 nscoord specUndist
= specTotal
;
363 for (PRInt32 col
= 0; col
< colCount
; ++col
) {
364 nsTableColFrame
*colFrame
= mTableFrame
->GetColFrame(col
);
366 NS_ERROR("column frames out of sync with cell map");
369 if (colFrame
->GetPrefPercent() == 0.0f
) {
370 NS_ASSERTION(colFrame
->GetFinalWidth() <= specUndist
,
371 "widths don't add up");
372 nscoord toAdd
= AllocateUnassigned(unassignedSpace
,
373 float(colFrame
->GetFinalWidth()) / float(specUndist
));
374 specUndist
-= colFrame
->GetFinalWidth();
375 colFrame
->SetFinalWidth(colFrame
->GetFinalWidth() + toAdd
);
376 unassignedSpace
-= toAdd
;
377 if (specUndist
<= 0) {
378 NS_ASSERTION(specUndist
== 0,
379 "math should be exact");
384 NS_ASSERTION(unassignedSpace
== 0, "failed to redistribute");
385 } else if (pctTotal
> 0) {
386 // Distribute proportionally to percentage columns.
387 float pctUndist
= pctTotal
;
388 for (PRInt32 col
= 0; col
< colCount
; ++col
) {
389 nsTableColFrame
*colFrame
= mTableFrame
->GetColFrame(col
);
391 NS_ERROR("column frames out of sync with cell map");
394 if (pctUndist
< colFrame
->GetPrefPercent()) {
395 // This can happen with floating-point math.
396 NS_ASSERTION(colFrame
->GetPrefPercent() - pctUndist
398 "widths don't add up");
399 pctUndist
= colFrame
->GetPrefPercent();
401 nscoord toAdd
= AllocateUnassigned(unassignedSpace
,
402 colFrame
->GetPrefPercent() / pctUndist
);
403 colFrame
->SetFinalWidth(colFrame
->GetFinalWidth() + toAdd
);
404 unassignedSpace
-= toAdd
;
405 pctUndist
-= colFrame
->GetPrefPercent();
406 if (pctUndist
<= 0.0f
) {
410 NS_ASSERTION(unassignedSpace
== 0, "failed to redistribute");
412 // Distribute equally to the zero-width columns.
413 PRInt32 colsLeft
= colCount
;
414 for (PRInt32 col
= 0; col
< colCount
; ++col
) {
415 nsTableColFrame
*colFrame
= mTableFrame
->GetColFrame(col
);
417 NS_ERROR("column frames out of sync with cell map");
420 NS_ASSERTION(colFrame
->GetFinalWidth() == 0, "yikes");
421 nscoord toAdd
= AllocateUnassigned(unassignedSpace
,
422 1.0f
/ float(colsLeft
));
423 colFrame
->SetFinalWidth(toAdd
);
424 unassignedSpace
-= toAdd
;
427 NS_ASSERTION(unassignedSpace
== 0, "failed to redistribute");
430 for (PRInt32 col
= 0; col
< colCount
; ++col
) {
431 nsTableColFrame
*colFrame
= mTableFrame
->GetColFrame(col
);
433 NS_ERROR("column frames out of sync with cell map");
436 if (oldColWidths
.ElementAt(col
) != colFrame
->GetFinalWidth()) {
437 mTableFrame
->DidResizeColumns();