layout/tables/FixedTableLayoutStrategy.cpp

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

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

mercurial