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