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 : * Algorithms that determine column and table widths used for CSS2's
41 : * 'table-layout: fixed'.
42 : */
43 :
44 : #include "FixedTableLayoutStrategy.h"
45 : #include "nsTableFrame.h"
46 : #include "nsTableColFrame.h"
47 : #include "nsTableCellFrame.h"
48 :
49 0 : FixedTableLayoutStrategy::FixedTableLayoutStrategy(nsTableFrame *aTableFrame)
50 : : nsITableLayoutStrategy(nsITableLayoutStrategy::Fixed)
51 0 : , mTableFrame(aTableFrame)
52 : {
53 0 : MarkIntrinsicWidthsDirty();
54 0 : }
55 :
56 : /* virtual */
57 0 : FixedTableLayoutStrategy::~FixedTableLayoutStrategy()
58 : {
59 0 : }
60 :
61 : /* virtual */ nscoord
62 0 : FixedTableLayoutStrategy::GetMinWidth(nsRenderingContext* aRenderingContext)
63 : {
64 0 : DISPLAY_MIN_WIDTH(mTableFrame, mMinWidth);
65 0 : if (mMinWidth != NS_INTRINSIC_WIDTH_UNKNOWN)
66 0 : return mMinWidth;
67 :
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).
79 :
80 : // XXX Should we really ignore 'min-width' and 'max-width'?
81 : // XXX Should we really ignore widths on column groups?
82 :
83 0 : nsTableCellMap *cellMap = mTableFrame->GetCellMap();
84 0 : PRInt32 colCount = cellMap->GetColCount();
85 0 : nscoord spacing = mTableFrame->GetCellSpacingX();
86 :
87 0 : nscoord result = 0;
88 :
89 0 : if (colCount > 0) {
90 0 : result += spacing * (colCount + 1);
91 : }
92 :
93 0 : for (PRInt32 col = 0; col < colCount; ++col) {
94 0 : nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
95 0 : if (!colFrame) {
96 0 : NS_ERROR("column frames out of sync with cell map");
97 0 : continue;
98 : }
99 : const nsStyleCoord *styleWidth =
100 0 : &colFrame->GetStylePosition()->mWidth;
101 0 : if (styleWidth->GetUnit() == eStyleUnit_Coord) {
102 : result += nsLayoutUtils::ComputeWidthValue(aRenderingContext,
103 0 : colFrame, 0, 0, 0, *styleWidth);
104 0 : } else if (styleWidth->GetUnit() == eStyleUnit_Percent) {
105 : // do nothing
106 : } else {
107 0 : NS_ASSERTION(styleWidth->GetUnit() == eStyleUnit_Auto ||
108 : styleWidth->GetUnit() == eStyleUnit_Enumerated ||
109 : styleWidth->IsCalcUnit(),
110 : "bad width");
111 :
112 : // The 'table-layout: fixed' algorithm considers only cells
113 : // in the first row.
114 : bool originates;
115 : PRInt32 colSpan;
116 : nsTableCellFrame *cellFrame =
117 0 : cellMap->GetCellInfoAt(0, col, &originates, &colSpan);
118 0 : if (cellFrame) {
119 0 : styleWidth = &cellFrame->GetStylePosition()->mWidth;
120 0 : if (styleWidth->GetUnit() == eStyleUnit_Coord ||
121 0 : (styleWidth->GetUnit() == eStyleUnit_Enumerated &&
122 0 : (styleWidth->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT ||
123 0 : styleWidth->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT))) {
124 : nscoord cellWidth = nsLayoutUtils::IntrinsicForContainer(
125 0 : aRenderingContext, cellFrame, nsLayoutUtils::MIN_WIDTH);
126 0 : if (colSpan > 1) {
127 : // If a column-spanning cell is in the first
128 : // row, split up the space evenly. (XXX This
129 : // isn't quite right if some of the columns it's
130 : // in have specified widths. Should we care?)
131 0 : cellWidth = ((cellWidth + spacing) / colSpan) - spacing;
132 : }
133 0 : result += cellWidth;
134 0 : } else if (styleWidth->GetUnit() == eStyleUnit_Percent) {
135 0 : if (colSpan > 1) {
136 : // XXX Can this force columns to negative
137 : // widths?
138 0 : result -= spacing * (colSpan - 1);
139 : }
140 : }
141 : // else, for 'auto', '-moz-available', '-moz-fit-content',
142 : // and 'calc()', do nothing
143 : }
144 : }
145 : }
146 :
147 0 : return (mMinWidth = result);
148 : }
149 :
150 : /* virtual */ nscoord
151 0 : FixedTableLayoutStrategy::GetPrefWidth(nsRenderingContext* aRenderingContext,
152 : bool aComputingSize)
153 : {
154 : // It's theoretically possible to do something much better here that
155 : // depends only on the columns and the first row (where we look at
156 : // intrinsic widths inside the first row and then reverse the
157 : // algorithm to find the narrowest width that would hold all of
158 : // those intrinsic widths), but it wouldn't be compatible with other
159 : // browsers.
160 0 : nscoord result = nscoord_MAX;
161 0 : DISPLAY_PREF_WIDTH(mTableFrame, result);
162 0 : return result;
163 : }
164 :
165 : /* virtual */ void
166 0 : FixedTableLayoutStrategy::MarkIntrinsicWidthsDirty()
167 : {
168 0 : mMinWidth = NS_INTRINSIC_WIDTH_UNKNOWN;
169 0 : mLastCalcWidth = nscoord_MIN;
170 0 : }
171 :
172 : static inline nscoord
173 0 : AllocateUnassigned(nscoord aUnassignedSpace, float aShare)
174 : {
175 0 : if (aShare == 1.0f) {
176 : // This happens when the numbers we're dividing to get aShare
177 : // are equal. We want to return unassignedSpace exactly, even
178 : // if it can't be precisely round-tripped through float.
179 0 : return aUnassignedSpace;
180 : }
181 0 : return NSToCoordRound(float(aUnassignedSpace) * aShare);
182 : }
183 :
184 : /* virtual */ void
185 0 : FixedTableLayoutStrategy::ComputeColumnWidths(const nsHTMLReflowState& aReflowState)
186 : {
187 0 : nscoord tableWidth = aReflowState.ComputedWidth();
188 :
189 0 : if (mLastCalcWidth == tableWidth)
190 0 : return;
191 0 : mLastCalcWidth = tableWidth;
192 :
193 0 : nsTableCellMap *cellMap = mTableFrame->GetCellMap();
194 0 : PRInt32 colCount = cellMap->GetColCount();
195 0 : nscoord spacing = mTableFrame->GetCellSpacingX();
196 :
197 0 : if (colCount == 0) {
198 : // No Columns - nothing to compute
199 0 : return;
200 : }
201 :
202 : // border-spacing isn't part of the basis for percentages.
203 0 : tableWidth -= spacing * (colCount + 1);
204 :
205 : // store the old column widths. We might call multiple times SetFinalWidth
206 : // on the columns, due to this we can't compare at the last call that the
207 : // width has changed with the respect to the last call to
208 : // ComputeColumnWidths. In order to overcome this we store the old values
209 : // in this array. A single call to SetFinalWidth would make it possible to
210 : // call GetFinalWidth before and to compare when setting the final width.
211 0 : nsTArray<nscoord> oldColWidths;
212 :
213 : // XXX This ignores the 'min-width' and 'max-width' properties
214 : // throughout. Then again, that's what the CSS spec says to do.
215 :
216 : // XXX Should we really ignore widths on column groups?
217 :
218 0 : PRUint32 unassignedCount = 0;
219 0 : nscoord unassignedSpace = tableWidth;
220 0 : const nscoord unassignedMarker = nscoord_MIN;
221 :
222 : // We use the PrefPercent on the columns to store the percentages
223 : // used to compute column widths in case we need to shrink or expand
224 : // the columns.
225 0 : float pctTotal = 0.0f;
226 :
227 : // Accumulate the total specified (non-percent) on the columns for
228 : // distributing excess width to the columns.
229 0 : nscoord specTotal = 0;
230 :
231 0 : for (PRInt32 col = 0; col < colCount; ++col) {
232 0 : nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
233 0 : if (!colFrame) {
234 0 : oldColWidths.AppendElement(0);
235 0 : NS_ERROR("column frames out of sync with cell map");
236 0 : continue;
237 : }
238 0 : oldColWidths.AppendElement(colFrame->GetFinalWidth());
239 0 : colFrame->ResetPrefPercent();
240 : const nsStyleCoord *styleWidth =
241 0 : &colFrame->GetStylePosition()->mWidth;
242 : nscoord colWidth;
243 0 : if (styleWidth->GetUnit() == eStyleUnit_Coord) {
244 : colWidth = nsLayoutUtils::ComputeWidthValue(
245 : aReflowState.rendContext,
246 0 : colFrame, 0, 0, 0, *styleWidth);
247 0 : specTotal += colWidth;
248 0 : } else if (styleWidth->GetUnit() == eStyleUnit_Percent) {
249 0 : float pct = styleWidth->GetPercentValue();
250 0 : colWidth = NSToCoordFloor(pct * float(tableWidth));
251 0 : colFrame->AddPrefPercent(pct);
252 0 : pctTotal += pct;
253 : } else {
254 0 : NS_ASSERTION(styleWidth->GetUnit() == eStyleUnit_Auto ||
255 : styleWidth->GetUnit() == eStyleUnit_Enumerated ||
256 : styleWidth->IsCalcUnit(),
257 : "bad width");
258 :
259 : // The 'table-layout: fixed' algorithm considers only cells
260 : // in the first row.
261 : bool originates;
262 : PRInt32 colSpan;
263 : nsTableCellFrame *cellFrame =
264 0 : cellMap->GetCellInfoAt(0, col, &originates, &colSpan);
265 0 : if (cellFrame) {
266 0 : styleWidth = &cellFrame->GetStylePosition()->mWidth;
267 0 : if (styleWidth->GetUnit() == eStyleUnit_Coord ||
268 0 : (styleWidth->GetUnit() == eStyleUnit_Enumerated &&
269 0 : (styleWidth->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT ||
270 0 : styleWidth->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT))) {
271 : // XXX This should use real percentage padding
272 : // Note that the difference between MIN_WIDTH and
273 : // PREF_WIDTH shouldn't matter for any of these
274 : // values of styleWidth; use MIN_WIDTH for symmetry
275 : // with GetMinWidth above, just in case there is a
276 : // difference.
277 : colWidth = nsLayoutUtils::IntrinsicForContainer(
278 : aReflowState.rendContext,
279 0 : cellFrame, nsLayoutUtils::MIN_WIDTH);
280 0 : } else if (styleWidth->GetUnit() == eStyleUnit_Percent) {
281 : // XXX This should use real percentage padding
282 : nsIFrame::IntrinsicWidthOffsetData offsets =
283 0 : cellFrame->IntrinsicWidthOffsets(aReflowState.rendContext);
284 0 : float pct = styleWidth->GetPercentValue();
285 0 : colWidth = NSToCoordFloor(pct * float(tableWidth)) +
286 0 : offsets.hPadding + offsets.hBorder;
287 0 : pct /= float(colSpan);
288 0 : colFrame->AddPrefPercent(pct);
289 0 : pctTotal += pct;
290 : } else {
291 : // 'auto', '-moz-available', '-moz-fit-content', and
292 : // 'calc()'
293 0 : colWidth = unassignedMarker;
294 : }
295 0 : if (colWidth != unassignedMarker) {
296 0 : if (colSpan > 1) {
297 : // If a column-spanning cell is in the first
298 : // row, split up the space evenly. (XXX This
299 : // isn't quite right if some of the columns it's
300 : // in have specified widths. Should we care?)
301 0 : colWidth = ((colWidth + spacing) / colSpan) - spacing;
302 0 : if (colWidth < 0)
303 0 : colWidth = 0;
304 : }
305 0 : if (styleWidth->GetUnit() != eStyleUnit_Percent) {
306 0 : specTotal += colWidth;
307 : }
308 : }
309 : } else {
310 0 : colWidth = unassignedMarker;
311 : }
312 : }
313 :
314 0 : colFrame->SetFinalWidth(colWidth);
315 :
316 0 : if (colWidth == unassignedMarker) {
317 0 : ++unassignedCount;
318 : } else {
319 0 : unassignedSpace -= colWidth;
320 : }
321 : }
322 :
323 0 : if (unassignedSpace < 0) {
324 0 : if (pctTotal > 0) {
325 : // If the columns took up too much space, reduce those that
326 : // had percentage widths. The spec doesn't say to do this,
327 : // but we've always done it in the past, and so does WinIE6.
328 0 : nscoord pctUsed = NSToCoordFloor(pctTotal * float(tableWidth));
329 0 : nscoord reduce = NS_MIN(pctUsed, -unassignedSpace);
330 0 : float reduceRatio = float(reduce) / pctTotal;
331 0 : for (PRInt32 col = 0; col < colCount; ++col) {
332 0 : nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
333 0 : if (!colFrame) {
334 0 : NS_ERROR("column frames out of sync with cell map");
335 0 : continue;
336 : }
337 0 : nscoord colWidth = colFrame->GetFinalWidth();
338 0 : colWidth -= NSToCoordFloor(colFrame->GetPrefPercent() *
339 0 : reduceRatio);
340 0 : if (colWidth < 0)
341 0 : colWidth = 0;
342 0 : colFrame->SetFinalWidth(colWidth);
343 : }
344 : }
345 0 : unassignedSpace = 0;
346 : }
347 :
348 0 : if (unassignedCount > 0) {
349 : // The spec says to distribute the remaining space evenly among
350 : // the columns.
351 0 : nscoord toAssign = unassignedSpace / unassignedCount;
352 0 : for (PRInt32 col = 0; col < colCount; ++col) {
353 0 : nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
354 0 : if (!colFrame) {
355 0 : NS_ERROR("column frames out of sync with cell map");
356 0 : continue;
357 : }
358 0 : if (colFrame->GetFinalWidth() == unassignedMarker)
359 0 : colFrame->SetFinalWidth(toAssign);
360 : }
361 0 : } else if (unassignedSpace > 0) {
362 : // The spec doesn't say how to distribute the unassigned space.
363 0 : if (specTotal > 0) {
364 : // Distribute proportionally to non-percentage columns.
365 0 : nscoord specUndist = specTotal;
366 0 : for (PRInt32 col = 0; col < colCount; ++col) {
367 0 : nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
368 0 : if (!colFrame) {
369 0 : NS_ERROR("column frames out of sync with cell map");
370 0 : continue;
371 : }
372 0 : if (colFrame->GetPrefPercent() == 0.0f) {
373 0 : NS_ASSERTION(colFrame->GetFinalWidth() <= specUndist,
374 : "widths don't add up");
375 : nscoord toAdd = AllocateUnassigned(unassignedSpace,
376 0 : float(colFrame->GetFinalWidth()) / float(specUndist));
377 0 : specUndist -= colFrame->GetFinalWidth();
378 0 : colFrame->SetFinalWidth(colFrame->GetFinalWidth() + toAdd);
379 0 : unassignedSpace -= toAdd;
380 0 : if (specUndist <= 0) {
381 0 : NS_ASSERTION(specUndist == 0,
382 : "math should be exact");
383 0 : break;
384 : }
385 : }
386 : }
387 0 : NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
388 0 : } else if (pctTotal > 0) {
389 : // Distribute proportionally to percentage columns.
390 0 : float pctUndist = pctTotal;
391 0 : for (PRInt32 col = 0; col < colCount; ++col) {
392 0 : nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
393 0 : if (!colFrame) {
394 0 : NS_ERROR("column frames out of sync with cell map");
395 0 : continue;
396 : }
397 0 : if (pctUndist < colFrame->GetPrefPercent()) {
398 : // This can happen with floating-point math.
399 0 : NS_ASSERTION(colFrame->GetPrefPercent() - pctUndist
400 : < 0.0001,
401 : "widths don't add up");
402 0 : pctUndist = colFrame->GetPrefPercent();
403 : }
404 : nscoord toAdd = AllocateUnassigned(unassignedSpace,
405 0 : colFrame->GetPrefPercent() / pctUndist);
406 0 : colFrame->SetFinalWidth(colFrame->GetFinalWidth() + toAdd);
407 0 : unassignedSpace -= toAdd;
408 0 : pctUndist -= colFrame->GetPrefPercent();
409 0 : if (pctUndist <= 0.0f) {
410 0 : break;
411 : }
412 : }
413 0 : NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
414 : } else {
415 : // Distribute equally to the zero-width columns.
416 0 : PRInt32 colsLeft = colCount;
417 0 : for (PRInt32 col = 0; col < colCount; ++col) {
418 0 : nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
419 0 : if (!colFrame) {
420 0 : NS_ERROR("column frames out of sync with cell map");
421 0 : continue;
422 : }
423 0 : NS_ASSERTION(colFrame->GetFinalWidth() == 0, "yikes");
424 : nscoord toAdd = AllocateUnassigned(unassignedSpace,
425 0 : 1.0f / float(colsLeft));
426 0 : colFrame->SetFinalWidth(toAdd);
427 0 : unassignedSpace -= toAdd;
428 0 : --colsLeft;
429 : }
430 0 : NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
431 : }
432 : }
433 0 : for (PRInt32 col = 0; col < colCount; ++col) {
434 0 : nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
435 0 : if (!colFrame) {
436 0 : NS_ERROR("column frames out of sync with cell map");
437 0 : continue;
438 : }
439 0 : if (oldColWidths.ElementAt(col) != colFrame->GetFinalWidth()) {
440 0 : mTableFrame->DidResizeColumns();
441 0 : break;
442 : }
443 : }
444 : }
|