Bug 1869043 allow a device to be specified with MediaTrackGraph::NotifyWhenDeviceStar...
[gecko.git] / layout / tables / FixedTableLayoutStrategy.cpp
blob8d74e3ba12f286a55849cac2215fddbd83f019d3
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/. */
7 /*
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"
20 #include <algorithm>
22 using namespace mozilla;
24 FixedTableLayoutStrategy::FixedTableLayoutStrategy(nsTableFrame* aTableFrame)
25 : nsITableLayoutStrategy(nsITableLayoutStrategy::Fixed),
26 mTableFrame(aTableFrame) {
27 MarkIntrinsicISizesDirty();
30 /* virtual */
31 FixedTableLayoutStrategy::~FixedTableLayoutStrategy() = default;
33 /* virtual */
34 nscoord FixedTableLayoutStrategy::GetMinISize(gfxContext* aRenderingContext) {
35 DISPLAY_MIN_INLINE_SIZE(mTableFrame, mMinISize);
36 if (mMinISize != NS_INTRINSIC_ISIZE_UNKNOWN) {
37 return mMinISize;
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();
58 nscoord result = 0;
60 if (colCount > 0) {
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);
67 if (!colFrame) {
68 NS_ERROR("column frames out of sync with cell map");
69 continue;
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()) {
76 // do nothing
77 } else {
78 // The 'table-layout: fixed' algorithm considers only cells in the
79 // first row.
80 bool originates;
81 int32_t colSpan;
82 nsTableCellFrame* cellFrame =
83 cellMap->GetCellInfoAt(0, col, &originates, &colSpan);
84 if (cellFrame) {
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);
90 if (colSpan > 1) {
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
94 // we care?)
95 cellISize = ((cellISize + spacing) / colSpan) - spacing;
97 result += cellISize;
98 } else if (styleISize->ConvertsToPercentage()) {
99 if (colSpan > 1) {
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);
113 /* virtual */
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
121 // other browsers.
122 nscoord result = nscoord_MAX;
123 DISPLAY_PREF_INLINE_SIZE(mTableFrame, result);
124 return result;
127 /* virtual */
128 void FixedTableLayoutStrategy::MarkIntrinsicISizesDirty() {
129 mMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
130 mLastCalcISize = nscoord_MIN;
133 static inline nscoord AllocateUnassigned(nscoord aUnassignedSpace,
134 float aShare) {
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);
144 /* virtual */
145 void FixedTableLayoutStrategy::ComputeColumnISizes(
146 const ReflowInput& aReflowInput) {
147 nscoord tableISize = aReflowInput.ComputedISize();
149 if (mLastCalcISize == tableISize) {
150 return;
152 mLastCalcISize = tableISize;
154 nsTableCellMap* cellMap = mTableFrame->GetCellMap();
155 int32_t colCount = cellMap->GetColCount();
157 if (colCount == 0) {
158 // No Columns - nothing to compute
159 return;
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);
195 if (!colFrame) {
196 oldColISizes.AppendElement(0);
197 NS_ERROR("column frames out of sync with cell map");
198 continue;
200 oldColISizes.AppendElement(colFrame->GetFinalISize());
201 colFrame->ResetPrefPercent();
202 const auto* styleISize = &colFrame->StylePosition()->ISize(wm);
203 nscoord colISize;
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);
211 pctTotal += pct;
212 } else {
213 // The 'table-layout: fixed' algorithm considers only cells in the
214 // first row.
215 bool originates;
216 int32_t colSpan;
217 nsTableCellFrame* cellFrame =
218 cellMap->GetCellInfoAt(0, col, &originates, &colSpan);
219 if (cellFrame) {
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);
245 pctTotal += pct;
246 } else {
247 // 'auto', '-moz-available', '-moz-fit-content', and 'calc()'
248 // with percentages
249 colISize = unassignedMarker;
251 if (colISize != unassignedMarker) {
252 if (colSpan > 1) {
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
256 // care?)
257 nscoord spacing = mTableFrame->GetColSpacing(col);
258 colISize = ((colISize + spacing) / colSpan) - spacing;
259 if (colISize < 0) {
260 colISize = 0;
263 if (!styleISize->ConvertsToPercentage()) {
264 specTotal += colISize;
267 } else {
268 colISize = unassignedMarker;
272 colFrame->SetFinalISize(colISize);
274 if (colISize == unassignedMarker) {
275 ++unassignedCount;
276 } else {
277 unassignedSpace -= colISize;
281 if (unassignedSpace < 0) {
282 if (pctTotal > 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);
291 if (!colFrame) {
292 NS_ERROR("column frames out of sync with cell map");
293 continue;
295 nscoord colISize = colFrame->GetFinalISize();
296 colISize -= NSToCoordFloor(colFrame->GetPrefPercent() * reduceRatio);
297 if (colISize < 0) {
298 colISize = 0;
300 colFrame->SetFinalISize(colISize);
303 unassignedSpace = 0;
306 if (unassignedCount > 0) {
307 // The spec says to distribute the remaining space evenly among
308 // the columns.
309 nscoord toAssign = unassignedSpace / unassignedCount;
310 for (int32_t col = 0; col < colCount; ++col) {
311 nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
312 if (!colFrame) {
313 NS_ERROR("column frames out of sync with cell map");
314 continue;
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.
322 if (specTotal > 0) {
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);
327 if (!colFrame) {
328 NS_ERROR("column frames out of sync with cell map");
329 continue;
331 if (colFrame->GetPrefPercent() == 0.0f) {
332 NS_ASSERTION(colFrame->GetFinalISize() <= specUndist,
333 "inline sizes don't add up");
334 nscoord toAdd = AllocateUnassigned(
335 unassignedSpace,
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");
342 break;
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);
352 if (!colFrame) {
353 NS_ERROR("column frames out of sync with cell map");
354 continue;
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) {
368 break;
371 NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
372 } else {
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);
377 if (!colFrame) {
378 NS_ERROR("column frames out of sync with cell map");
379 continue;
381 NS_ASSERTION(colFrame->GetFinalISize() == 0, "yikes");
382 nscoord toAdd =
383 AllocateUnassigned(unassignedSpace, 1.0f / float(colsRemaining));
384 colFrame->SetFinalISize(toAdd);
385 unassignedSpace -= toAdd;
386 --colsRemaining;
388 NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
391 for (int32_t col = 0; col < colCount; ++col) {
392 nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
393 if (!colFrame) {
394 NS_ERROR("column frames out of sync with cell map");
395 continue;
397 if (oldColISizes.ElementAt(col) != colFrame->GetFinalISize()) {
398 mTableFrame->DidResizeColumns();
399 break;