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/. */
8 * Algorithms that determine column and table inline sizes used for
9 * CSS2's 'table-layout: fixed'.
12 #include "FixedTableLayoutStrategy.h"
14 #include "nsLayoutUtils.h"
15 #include "nsStyleConsts.h"
16 #include "nsTableFrame.h"
17 #include "nsTableColFrame.h"
18 #include "nsTableCellFrame.h"
19 #include "WritingModes.h"
22 using namespace mozilla
;
24 FixedTableLayoutStrategy::FixedTableLayoutStrategy(nsTableFrame
* aTableFrame
)
25 : nsITableLayoutStrategy(nsITableLayoutStrategy::Fixed
),
26 mTableFrame(aTableFrame
) {
27 MarkIntrinsicISizesDirty();
31 FixedTableLayoutStrategy::~FixedTableLayoutStrategy() = default;
34 nscoord
FixedTableLayoutStrategy::GetMinISize(gfxContext
* aRenderingContext
) {
35 DISPLAY_MIN_INLINE_SIZE(mTableFrame
, mMinISize
);
36 if (mMinISize
!= NS_INTRINSIC_ISIZE_UNKNOWN
) {
40 // It's theoretically possible to do something much better here that
41 // depends only on the columns and the first row (where we look at
42 // intrinsic inline sizes inside the first row and then reverse the
43 // algorithm to find the narrowest inline size that would hold all of
44 // those intrinsic inline sizes), but it wouldn't be compatible with
45 // other browsers, or with the use of GetMinISize by
46 // nsTableFrame::ComputeSize to determine the inline size of a fixed
47 // layout table, since CSS2.1 says:
48 // The width of the table is then the greater of the value of the
49 // 'width' property for the table element and the sum of the column
50 // widths (plus cell spacing or borders).
52 // XXX Should we really ignore 'min-inline-size' and 'max-inline-size'?
53 // XXX Should we really ignore inline sizes on column groups?
55 nsTableCellMap
* cellMap
= mTableFrame
->GetCellMap();
56 int32_t colCount
= cellMap
->GetColCount();
61 result
+= mTableFrame
->GetColSpacing(-1, colCount
);
64 WritingMode wm
= mTableFrame
->GetWritingMode();
65 for (int32_t col
= 0; col
< colCount
; ++col
) {
66 nsTableColFrame
* colFrame
= mTableFrame
->GetColFrame(col
);
68 NS_ERROR("column frames out of sync with cell map");
71 nscoord spacing
= mTableFrame
->GetColSpacing(col
);
72 const auto* styleISize
= &colFrame
->StylePosition()->ISize(wm
);
73 if (styleISize
->ConvertsToLength()) {
74 result
+= styleISize
->ToLength();
75 } else if (styleISize
->ConvertsToPercentage()) {
78 // The 'table-layout: fixed' algorithm considers only cells in the
82 nsTableCellFrame
* cellFrame
=
83 cellMap
->GetCellInfoAt(0, col
, &originates
, &colSpan
);
85 styleISize
= &cellFrame
->StylePosition()->ISize(wm
);
86 if (styleISize
->ConvertsToLength() || styleISize
->IsMinContent() ||
87 styleISize
->IsMaxContent()) {
88 nscoord cellISize
= nsLayoutUtils::IntrinsicForContainer(
89 aRenderingContext
, cellFrame
, IntrinsicISizeType::MinISize
);
91 // If a column-spanning cell is in the first row, split up
92 // the space evenly. (XXX This isn't quite right if some of
93 // the columns it's in have specified inline sizes. Should
95 cellISize
= ((cellISize
+ spacing
) / colSpan
) - spacing
;
98 } else if (styleISize
->ConvertsToPercentage()) {
100 // XXX Can this force columns to negative inline sizes?
101 result
-= spacing
* (colSpan
- 1);
104 // else, for 'auto', '-moz-available', '-moz-fit-content',
105 // and 'calc()' with both lengths and percentages, do nothing
110 return (mMinISize
= result
);
114 nscoord
FixedTableLayoutStrategy::GetPrefISize(gfxContext
* aRenderingContext
,
115 bool aComputingSize
) {
116 // It's theoretically possible to do something much better here that
117 // depends only on the columns and the first row (where we look at
118 // intrinsic inline sizes inside the first row and then reverse the
119 // algorithm to find the narrowest inline size that would hold all of
120 // those intrinsic inline sizes), but it wouldn't be compatible with
122 nscoord result
= nscoord_MAX
;
123 DISPLAY_PREF_INLINE_SIZE(mTableFrame
, result
);
128 void FixedTableLayoutStrategy::MarkIntrinsicISizesDirty() {
129 mMinISize
= NS_INTRINSIC_ISIZE_UNKNOWN
;
130 mLastCalcISize
= nscoord_MIN
;
133 static inline nscoord
AllocateUnassigned(nscoord aUnassignedSpace
,
135 if (aShare
== 1.0f
) {
136 // This happens when the numbers we're dividing to get aShare are
137 // equal. We want to return unassignedSpace exactly, even if it
138 // can't be precisely round-tripped through float.
139 return aUnassignedSpace
;
141 return NSToCoordRound(float(aUnassignedSpace
) * aShare
);
145 void FixedTableLayoutStrategy::ComputeColumnISizes(
146 const ReflowInput
& aReflowInput
) {
147 nscoord tableISize
= aReflowInput
.ComputedISize();
149 if (mLastCalcISize
== tableISize
) {
152 mLastCalcISize
= tableISize
;
154 nsTableCellMap
* cellMap
= mTableFrame
->GetCellMap();
155 int32_t colCount
= cellMap
->GetColCount();
158 // No Columns - nothing to compute
162 // border-spacing isn't part of the basis for percentages.
163 tableISize
-= mTableFrame
->GetColSpacing(-1, colCount
);
165 // store the old column inline sizes. We might call SetFinalISize
166 // multiple times on the columns, due to this we can't compare at the
167 // last call that the inline size has changed with respect to the last
168 // call to ComputeColumnISizes. In order to overcome this we store the
169 // old values in this array. A single call to SetFinalISize would make
170 // it possible to call GetFinalISize before and to compare when
171 // setting the final inline size.
172 nsTArray
<nscoord
> oldColISizes
;
174 // XXX This ignores the 'min-width' and 'max-width' properties
175 // throughout. Then again, that's what the CSS spec says to do.
177 // XXX Should we really ignore widths on column groups?
179 uint32_t unassignedCount
= 0;
180 nscoord unassignedSpace
= tableISize
;
181 const nscoord unassignedMarker
= nscoord_MIN
;
183 // We use the PrefPercent on the columns to store the percentages
184 // used to compute column inline sizes in case we need to shrink or
185 // expand the columns.
186 float pctTotal
= 0.0f
;
188 // Accumulate the total specified (non-percent) on the columns for
189 // distributing excess inline size to the columns.
190 nscoord specTotal
= 0;
192 WritingMode wm
= mTableFrame
->GetWritingMode();
193 for (int32_t col
= 0; col
< colCount
; ++col
) {
194 nsTableColFrame
* colFrame
= mTableFrame
->GetColFrame(col
);
196 oldColISizes
.AppendElement(0);
197 NS_ERROR("column frames out of sync with cell map");
200 oldColISizes
.AppendElement(colFrame
->GetFinalISize());
201 colFrame
->ResetPrefPercent();
202 const auto* styleISize
= &colFrame
->StylePosition()->ISize(wm
);
204 if (styleISize
->ConvertsToLength()) {
205 colISize
= styleISize
->ToLength();
206 specTotal
+= colISize
;
207 } else if (styleISize
->ConvertsToPercentage()) {
208 float pct
= styleISize
->ToPercentage();
209 colISize
= NSToCoordFloor(pct
* float(tableISize
));
210 colFrame
->AddPrefPercent(pct
);
213 // The 'table-layout: fixed' algorithm considers only cells in the
217 nsTableCellFrame
* cellFrame
=
218 cellMap
->GetCellInfoAt(0, col
, &originates
, &colSpan
);
220 const nsStylePosition
* cellStylePos
= cellFrame
->StylePosition();
221 styleISize
= &cellStylePos
->ISize(wm
);
222 if (styleISize
->ConvertsToLength() || styleISize
->IsMaxContent() ||
223 styleISize
->IsMinContent()) {
224 // XXX This should use real percentage padding
225 // Note that the difference between MinISize and PrefISize
226 // shouldn't matter for any of these values of styleISize; use
227 // MIN_ISIZE for symmetry with GetMinISize above, just in case
228 // there is a difference.
229 colISize
= nsLayoutUtils::IntrinsicForContainer(
230 aReflowInput
.mRenderingContext
, cellFrame
,
231 IntrinsicISizeType::MinISize
);
232 } else if (styleISize
->ConvertsToPercentage()) {
233 // XXX This should use real percentage padding
234 float pct
= styleISize
->ToPercentage();
235 colISize
= NSToCoordFloor(pct
* float(tableISize
));
237 if (cellStylePos
->mBoxSizing
== StyleBoxSizing::Content
) {
238 nsIFrame::IntrinsicSizeOffsetData offsets
=
239 cellFrame
->IntrinsicISizeOffsets();
240 colISize
+= offsets
.padding
+ offsets
.border
;
243 pct
/= float(colSpan
);
244 colFrame
->AddPrefPercent(pct
);
247 // 'auto', '-moz-available', '-moz-fit-content', and 'calc()'
249 colISize
= unassignedMarker
;
251 if (colISize
!= unassignedMarker
) {
253 // If a column-spanning cell is in the first row, split up
254 // the space evenly. (XXX This isn't quite right if some of
255 // the columns it's in have specified iSizes. Should we
257 nscoord spacing
= mTableFrame
->GetColSpacing(col
);
258 colISize
= ((colISize
+ spacing
) / colSpan
) - spacing
;
263 if (!styleISize
->ConvertsToPercentage()) {
264 specTotal
+= colISize
;
268 colISize
= unassignedMarker
;
272 colFrame
->SetFinalISize(colISize
);
274 if (colISize
== unassignedMarker
) {
277 unassignedSpace
-= colISize
;
281 if (unassignedSpace
< 0) {
283 // If the columns took up too much space, reduce those that had
284 // percentage inline sizes. The spec doesn't say to do this, but
285 // we've always done it in the past, and so does WinIE6.
286 nscoord pctUsed
= NSToCoordFloor(pctTotal
* float(tableISize
));
287 nscoord reduce
= std::min(pctUsed
, -unassignedSpace
);
288 float reduceRatio
= float(reduce
) / pctTotal
;
289 for (int32_t col
= 0; col
< colCount
; ++col
) {
290 nsTableColFrame
* colFrame
= mTableFrame
->GetColFrame(col
);
292 NS_ERROR("column frames out of sync with cell map");
295 nscoord colISize
= colFrame
->GetFinalISize();
296 colISize
-= NSToCoordFloor(colFrame
->GetPrefPercent() * reduceRatio
);
300 colFrame
->SetFinalISize(colISize
);
306 if (unassignedCount
> 0) {
307 // The spec says to distribute the remaining space evenly among
309 nscoord toAssign
= unassignedSpace
/ unassignedCount
;
310 for (int32_t col
= 0; col
< colCount
; ++col
) {
311 nsTableColFrame
* colFrame
= mTableFrame
->GetColFrame(col
);
313 NS_ERROR("column frames out of sync with cell map");
316 if (colFrame
->GetFinalISize() == unassignedMarker
) {
317 colFrame
->SetFinalISize(toAssign
);
320 } else if (unassignedSpace
> 0) {
321 // The spec doesn't say how to distribute the unassigned space.
323 // Distribute proportionally to non-percentage columns.
324 nscoord specUndist
= specTotal
;
325 for (int32_t col
= 0; col
< colCount
; ++col
) {
326 nsTableColFrame
* colFrame
= mTableFrame
->GetColFrame(col
);
328 NS_ERROR("column frames out of sync with cell map");
331 if (colFrame
->GetPrefPercent() == 0.0f
) {
332 NS_ASSERTION(colFrame
->GetFinalISize() <= specUndist
,
333 "inline sizes don't add up");
334 nscoord toAdd
= AllocateUnassigned(
336 float(colFrame
->GetFinalISize()) / float(specUndist
));
337 specUndist
-= colFrame
->GetFinalISize();
338 colFrame
->SetFinalISize(colFrame
->GetFinalISize() + toAdd
);
339 unassignedSpace
-= toAdd
;
340 if (specUndist
<= 0) {
341 NS_ASSERTION(specUndist
== 0, "math should be exact");
346 NS_ASSERTION(unassignedSpace
== 0, "failed to redistribute");
347 } else if (pctTotal
> 0) {
348 // Distribute proportionally to percentage columns.
349 float pctUndist
= pctTotal
;
350 for (int32_t col
= 0; col
< colCount
; ++col
) {
351 nsTableColFrame
* colFrame
= mTableFrame
->GetColFrame(col
);
353 NS_ERROR("column frames out of sync with cell map");
356 if (pctUndist
< colFrame
->GetPrefPercent()) {
357 // This can happen with floating-point math.
358 NS_ASSERTION(colFrame
->GetPrefPercent() - pctUndist
< 0.0001,
359 "inline sizes don't add up");
360 pctUndist
= colFrame
->GetPrefPercent();
362 nscoord toAdd
= AllocateUnassigned(
363 unassignedSpace
, colFrame
->GetPrefPercent() / pctUndist
);
364 colFrame
->SetFinalISize(colFrame
->GetFinalISize() + toAdd
);
365 unassignedSpace
-= toAdd
;
366 pctUndist
-= colFrame
->GetPrefPercent();
367 if (pctUndist
<= 0.0f
) {
371 NS_ASSERTION(unassignedSpace
== 0, "failed to redistribute");
373 // Distribute equally to the zero-iSize columns.
374 int32_t colsRemaining
= colCount
;
375 for (int32_t col
= 0; col
< colCount
; ++col
) {
376 nsTableColFrame
* colFrame
= mTableFrame
->GetColFrame(col
);
378 NS_ERROR("column frames out of sync with cell map");
381 NS_ASSERTION(colFrame
->GetFinalISize() == 0, "yikes");
383 AllocateUnassigned(unassignedSpace
, 1.0f
/ float(colsRemaining
));
384 colFrame
->SetFinalISize(toAdd
);
385 unassignedSpace
-= toAdd
;
388 NS_ASSERTION(unassignedSpace
== 0, "failed to redistribute");
391 for (int32_t col
= 0; col
< colCount
; ++col
) {
392 nsTableColFrame
* colFrame
= mTableFrame
->GetColFrame(col
);
394 NS_ERROR("column frames out of sync with cell map");
397 if (oldColISizes
.ElementAt(col
) != colFrame
->GetFinalISize()) {
398 mTableFrame
->DidResizeColumns();