1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/tables/FixedTableLayoutStrategy.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,427 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +// vim:cindent:ts=4:et:sw=4: 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/* 1.11 + * Algorithms that determine column and table widths used for CSS2's 1.12 + * 'table-layout: fixed'. 1.13 + */ 1.14 + 1.15 +#include "FixedTableLayoutStrategy.h" 1.16 +#include "nsTableFrame.h" 1.17 +#include "nsTableColFrame.h" 1.18 +#include "nsTableCellFrame.h" 1.19 +#include <algorithm> 1.20 + 1.21 +FixedTableLayoutStrategy::FixedTableLayoutStrategy(nsTableFrame *aTableFrame) 1.22 + : nsITableLayoutStrategy(nsITableLayoutStrategy::Fixed) 1.23 + , mTableFrame(aTableFrame) 1.24 +{ 1.25 + MarkIntrinsicWidthsDirty(); 1.26 +} 1.27 + 1.28 +/* virtual */ 1.29 +FixedTableLayoutStrategy::~FixedTableLayoutStrategy() 1.30 +{ 1.31 +} 1.32 + 1.33 +/* virtual */ nscoord 1.34 +FixedTableLayoutStrategy::GetMinWidth(nsRenderingContext* aRenderingContext) 1.35 +{ 1.36 + DISPLAY_MIN_WIDTH(mTableFrame, mMinWidth); 1.37 + if (mMinWidth != NS_INTRINSIC_WIDTH_UNKNOWN) 1.38 + return mMinWidth; 1.39 + 1.40 + // It's theoretically possible to do something much better here that 1.41 + // depends only on the columns and the first row (where we look at 1.42 + // intrinsic widths inside the first row and then reverse the 1.43 + // algorithm to find the narrowest width that would hold all of 1.44 + // those intrinsic widths), but it wouldn't be compatible with other 1.45 + // browsers, or with the use of GetMinWidth by 1.46 + // nsTableFrame::ComputeSize to determine the width of a fixed 1.47 + // layout table, since CSS2.1 says: 1.48 + // The width of the table is then the greater of the value of the 1.49 + // 'width' property for the table element and the sum of the 1.50 + // column widths (plus cell spacing or borders). 1.51 + 1.52 + // XXX Should we really ignore 'min-width' and 'max-width'? 1.53 + // XXX Should we really ignore widths on column groups? 1.54 + 1.55 + nsTableCellMap *cellMap = mTableFrame->GetCellMap(); 1.56 + int32_t colCount = cellMap->GetColCount(); 1.57 + nscoord spacing = mTableFrame->GetCellSpacingX(); 1.58 + 1.59 + nscoord result = 0; 1.60 + 1.61 + if (colCount > 0) { 1.62 + result += spacing * (colCount + 1); 1.63 + } 1.64 + 1.65 + for (int32_t col = 0; col < colCount; ++col) { 1.66 + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); 1.67 + if (!colFrame) { 1.68 + NS_ERROR("column frames out of sync with cell map"); 1.69 + continue; 1.70 + } 1.71 + const nsStyleCoord *styleWidth = 1.72 + &colFrame->StylePosition()->mWidth; 1.73 + if (styleWidth->ConvertsToLength()) { 1.74 + result += nsLayoutUtils::ComputeWidthValue(aRenderingContext, 1.75 + colFrame, 0, 0, 0, *styleWidth); 1.76 + } else if (styleWidth->GetUnit() == eStyleUnit_Percent) { 1.77 + // do nothing 1.78 + } else { 1.79 + NS_ASSERTION(styleWidth->GetUnit() == eStyleUnit_Auto || 1.80 + styleWidth->GetUnit() == eStyleUnit_Enumerated || 1.81 + (styleWidth->IsCalcUnit() && styleWidth->CalcHasPercent()), 1.82 + "bad width"); 1.83 + 1.84 + // The 'table-layout: fixed' algorithm considers only cells 1.85 + // in the first row. 1.86 + bool originates; 1.87 + int32_t colSpan; 1.88 + nsTableCellFrame *cellFrame = 1.89 + cellMap->GetCellInfoAt(0, col, &originates, &colSpan); 1.90 + if (cellFrame) { 1.91 + styleWidth = &cellFrame->StylePosition()->mWidth; 1.92 + if (styleWidth->ConvertsToLength() || 1.93 + (styleWidth->GetUnit() == eStyleUnit_Enumerated && 1.94 + (styleWidth->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT || 1.95 + styleWidth->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT))) { 1.96 + nscoord cellWidth = nsLayoutUtils::IntrinsicForContainer( 1.97 + aRenderingContext, cellFrame, nsLayoutUtils::MIN_WIDTH); 1.98 + if (colSpan > 1) { 1.99 + // If a column-spanning cell is in the first 1.100 + // row, split up the space evenly. (XXX This 1.101 + // isn't quite right if some of the columns it's 1.102 + // in have specified widths. Should we care?) 1.103 + cellWidth = ((cellWidth + spacing) / colSpan) - spacing; 1.104 + } 1.105 + result += cellWidth; 1.106 + } else if (styleWidth->GetUnit() == eStyleUnit_Percent) { 1.107 + if (colSpan > 1) { 1.108 + // XXX Can this force columns to negative 1.109 + // widths? 1.110 + result -= spacing * (colSpan - 1); 1.111 + } 1.112 + } 1.113 + // else, for 'auto', '-moz-available', '-moz-fit-content', 1.114 + // and 'calc()' with percentages, do nothing 1.115 + } 1.116 + } 1.117 + } 1.118 + 1.119 + return (mMinWidth = result); 1.120 +} 1.121 + 1.122 +/* virtual */ nscoord 1.123 +FixedTableLayoutStrategy::GetPrefWidth(nsRenderingContext* aRenderingContext, 1.124 + bool aComputingSize) 1.125 +{ 1.126 + // It's theoretically possible to do something much better here that 1.127 + // depends only on the columns and the first row (where we look at 1.128 + // intrinsic widths inside the first row and then reverse the 1.129 + // algorithm to find the narrowest width that would hold all of 1.130 + // those intrinsic widths), but it wouldn't be compatible with other 1.131 + // browsers. 1.132 + nscoord result = nscoord_MAX; 1.133 + DISPLAY_PREF_WIDTH(mTableFrame, result); 1.134 + return result; 1.135 +} 1.136 + 1.137 +/* virtual */ void 1.138 +FixedTableLayoutStrategy::MarkIntrinsicWidthsDirty() 1.139 +{ 1.140 + mMinWidth = NS_INTRINSIC_WIDTH_UNKNOWN; 1.141 + mLastCalcWidth = nscoord_MIN; 1.142 +} 1.143 + 1.144 +static inline nscoord 1.145 +AllocateUnassigned(nscoord aUnassignedSpace, float aShare) 1.146 +{ 1.147 + if (aShare == 1.0f) { 1.148 + // This happens when the numbers we're dividing to get aShare 1.149 + // are equal. We want to return unassignedSpace exactly, even 1.150 + // if it can't be precisely round-tripped through float. 1.151 + return aUnassignedSpace; 1.152 + } 1.153 + return NSToCoordRound(float(aUnassignedSpace) * aShare); 1.154 +} 1.155 + 1.156 +/* virtual */ void 1.157 +FixedTableLayoutStrategy::ComputeColumnWidths(const nsHTMLReflowState& aReflowState) 1.158 +{ 1.159 + nscoord tableWidth = aReflowState.ComputedWidth(); 1.160 + 1.161 + if (mLastCalcWidth == tableWidth) 1.162 + return; 1.163 + mLastCalcWidth = tableWidth; 1.164 + 1.165 + nsTableCellMap *cellMap = mTableFrame->GetCellMap(); 1.166 + int32_t colCount = cellMap->GetColCount(); 1.167 + nscoord spacing = mTableFrame->GetCellSpacingX(); 1.168 + 1.169 + if (colCount == 0) { 1.170 + // No Columns - nothing to compute 1.171 + return; 1.172 + } 1.173 + 1.174 + // border-spacing isn't part of the basis for percentages. 1.175 + tableWidth -= spacing * (colCount + 1); 1.176 + 1.177 + // store the old column widths. We might call multiple times SetFinalWidth 1.178 + // on the columns, due to this we can't compare at the last call that the 1.179 + // width has changed with the respect to the last call to 1.180 + // ComputeColumnWidths. In order to overcome this we store the old values 1.181 + // in this array. A single call to SetFinalWidth would make it possible to 1.182 + // call GetFinalWidth before and to compare when setting the final width. 1.183 + nsTArray<nscoord> oldColWidths; 1.184 + 1.185 + // XXX This ignores the 'min-width' and 'max-width' properties 1.186 + // throughout. Then again, that's what the CSS spec says to do. 1.187 + 1.188 + // XXX Should we really ignore widths on column groups? 1.189 + 1.190 + uint32_t unassignedCount = 0; 1.191 + nscoord unassignedSpace = tableWidth; 1.192 + const nscoord unassignedMarker = nscoord_MIN; 1.193 + 1.194 + // We use the PrefPercent on the columns to store the percentages 1.195 + // used to compute column widths in case we need to shrink or expand 1.196 + // the columns. 1.197 + float pctTotal = 0.0f; 1.198 + 1.199 + // Accumulate the total specified (non-percent) on the columns for 1.200 + // distributing excess width to the columns. 1.201 + nscoord specTotal = 0; 1.202 + 1.203 + for (int32_t col = 0; col < colCount; ++col) { 1.204 + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); 1.205 + if (!colFrame) { 1.206 + oldColWidths.AppendElement(0); 1.207 + NS_ERROR("column frames out of sync with cell map"); 1.208 + continue; 1.209 + } 1.210 + oldColWidths.AppendElement(colFrame->GetFinalWidth()); 1.211 + colFrame->ResetPrefPercent(); 1.212 + const nsStyleCoord *styleWidth = 1.213 + &colFrame->StylePosition()->mWidth; 1.214 + nscoord colWidth; 1.215 + if (styleWidth->ConvertsToLength()) { 1.216 + colWidth = nsLayoutUtils::ComputeWidthValue( 1.217 + aReflowState.rendContext, 1.218 + colFrame, 0, 0, 0, *styleWidth); 1.219 + specTotal += colWidth; 1.220 + } else if (styleWidth->GetUnit() == eStyleUnit_Percent) { 1.221 + float pct = styleWidth->GetPercentValue(); 1.222 + colWidth = NSToCoordFloor(pct * float(tableWidth)); 1.223 + colFrame->AddPrefPercent(pct); 1.224 + pctTotal += pct; 1.225 + } else { 1.226 + NS_ASSERTION(styleWidth->GetUnit() == eStyleUnit_Auto || 1.227 + styleWidth->GetUnit() == eStyleUnit_Enumerated || 1.228 + (styleWidth->IsCalcUnit() && styleWidth->CalcHasPercent()), 1.229 + "bad width"); 1.230 + 1.231 + // The 'table-layout: fixed' algorithm considers only cells 1.232 + // in the first row. 1.233 + bool originates; 1.234 + int32_t colSpan; 1.235 + nsTableCellFrame *cellFrame = 1.236 + cellMap->GetCellInfoAt(0, col, &originates, &colSpan); 1.237 + if (cellFrame) { 1.238 + styleWidth = &cellFrame->StylePosition()->mWidth; 1.239 + if (styleWidth->ConvertsToLength() || 1.240 + (styleWidth->GetUnit() == eStyleUnit_Enumerated && 1.241 + (styleWidth->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT || 1.242 + styleWidth->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT))) { 1.243 + // XXX This should use real percentage padding 1.244 + // Note that the difference between MIN_WIDTH and 1.245 + // PREF_WIDTH shouldn't matter for any of these 1.246 + // values of styleWidth; use MIN_WIDTH for symmetry 1.247 + // with GetMinWidth above, just in case there is a 1.248 + // difference. 1.249 + colWidth = nsLayoutUtils::IntrinsicForContainer( 1.250 + aReflowState.rendContext, 1.251 + cellFrame, nsLayoutUtils::MIN_WIDTH); 1.252 + } else if (styleWidth->GetUnit() == eStyleUnit_Percent) { 1.253 + // XXX This should use real percentage padding 1.254 + nsIFrame::IntrinsicWidthOffsetData offsets = 1.255 + cellFrame->IntrinsicWidthOffsets(aReflowState.rendContext); 1.256 + float pct = styleWidth->GetPercentValue(); 1.257 + colWidth = NSToCoordFloor(pct * float(tableWidth)); 1.258 + 1.259 + nscoord boxSizingAdjust = 0; 1.260 + switch (cellFrame->StylePosition()->mBoxSizing) { 1.261 + case NS_STYLE_BOX_SIZING_CONTENT: 1.262 + boxSizingAdjust += offsets.hPadding; 1.263 + // Fall through 1.264 + case NS_STYLE_BOX_SIZING_PADDING: 1.265 + boxSizingAdjust += offsets.hBorder; 1.266 + // Fall through 1.267 + case NS_STYLE_BOX_SIZING_BORDER: 1.268 + // Don't add anything 1.269 + break; 1.270 + } 1.271 + colWidth += boxSizingAdjust; 1.272 + 1.273 + pct /= float(colSpan); 1.274 + colFrame->AddPrefPercent(pct); 1.275 + pctTotal += pct; 1.276 + } else { 1.277 + // 'auto', '-moz-available', '-moz-fit-content', and 1.278 + // 'calc()' with percentages 1.279 + colWidth = unassignedMarker; 1.280 + } 1.281 + if (colWidth != unassignedMarker) { 1.282 + if (colSpan > 1) { 1.283 + // If a column-spanning cell is in the first 1.284 + // row, split up the space evenly. (XXX This 1.285 + // isn't quite right if some of the columns it's 1.286 + // in have specified widths. Should we care?) 1.287 + colWidth = ((colWidth + spacing) / colSpan) - spacing; 1.288 + if (colWidth < 0) 1.289 + colWidth = 0; 1.290 + } 1.291 + if (styleWidth->GetUnit() != eStyleUnit_Percent) { 1.292 + specTotal += colWidth; 1.293 + } 1.294 + } 1.295 + } else { 1.296 + colWidth = unassignedMarker; 1.297 + } 1.298 + } 1.299 + 1.300 + colFrame->SetFinalWidth(colWidth); 1.301 + 1.302 + if (colWidth == unassignedMarker) { 1.303 + ++unassignedCount; 1.304 + } else { 1.305 + unassignedSpace -= colWidth; 1.306 + } 1.307 + } 1.308 + 1.309 + if (unassignedSpace < 0) { 1.310 + if (pctTotal > 0) { 1.311 + // If the columns took up too much space, reduce those that 1.312 + // had percentage widths. The spec doesn't say to do this, 1.313 + // but we've always done it in the past, and so does WinIE6. 1.314 + nscoord pctUsed = NSToCoordFloor(pctTotal * float(tableWidth)); 1.315 + nscoord reduce = std::min(pctUsed, -unassignedSpace); 1.316 + float reduceRatio = float(reduce) / pctTotal; 1.317 + for (int32_t col = 0; col < colCount; ++col) { 1.318 + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); 1.319 + if (!colFrame) { 1.320 + NS_ERROR("column frames out of sync with cell map"); 1.321 + continue; 1.322 + } 1.323 + nscoord colWidth = colFrame->GetFinalWidth(); 1.324 + colWidth -= NSToCoordFloor(colFrame->GetPrefPercent() * 1.325 + reduceRatio); 1.326 + if (colWidth < 0) 1.327 + colWidth = 0; 1.328 + colFrame->SetFinalWidth(colWidth); 1.329 + } 1.330 + } 1.331 + unassignedSpace = 0; 1.332 + } 1.333 + 1.334 + if (unassignedCount > 0) { 1.335 + // The spec says to distribute the remaining space evenly among 1.336 + // the columns. 1.337 + nscoord toAssign = unassignedSpace / unassignedCount; 1.338 + for (int32_t col = 0; col < colCount; ++col) { 1.339 + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); 1.340 + if (!colFrame) { 1.341 + NS_ERROR("column frames out of sync with cell map"); 1.342 + continue; 1.343 + } 1.344 + if (colFrame->GetFinalWidth() == unassignedMarker) 1.345 + colFrame->SetFinalWidth(toAssign); 1.346 + } 1.347 + } else if (unassignedSpace > 0) { 1.348 + // The spec doesn't say how to distribute the unassigned space. 1.349 + if (specTotal > 0) { 1.350 + // Distribute proportionally to non-percentage columns. 1.351 + nscoord specUndist = specTotal; 1.352 + for (int32_t col = 0; col < colCount; ++col) { 1.353 + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); 1.354 + if (!colFrame) { 1.355 + NS_ERROR("column frames out of sync with cell map"); 1.356 + continue; 1.357 + } 1.358 + if (colFrame->GetPrefPercent() == 0.0f) { 1.359 + NS_ASSERTION(colFrame->GetFinalWidth() <= specUndist, 1.360 + "widths don't add up"); 1.361 + nscoord toAdd = AllocateUnassigned(unassignedSpace, 1.362 + float(colFrame->GetFinalWidth()) / float(specUndist)); 1.363 + specUndist -= colFrame->GetFinalWidth(); 1.364 + colFrame->SetFinalWidth(colFrame->GetFinalWidth() + toAdd); 1.365 + unassignedSpace -= toAdd; 1.366 + if (specUndist <= 0) { 1.367 + NS_ASSERTION(specUndist == 0, 1.368 + "math should be exact"); 1.369 + break; 1.370 + } 1.371 + } 1.372 + } 1.373 + NS_ASSERTION(unassignedSpace == 0, "failed to redistribute"); 1.374 + } else if (pctTotal > 0) { 1.375 + // Distribute proportionally to percentage columns. 1.376 + float pctUndist = pctTotal; 1.377 + for (int32_t col = 0; col < colCount; ++col) { 1.378 + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); 1.379 + if (!colFrame) { 1.380 + NS_ERROR("column frames out of sync with cell map"); 1.381 + continue; 1.382 + } 1.383 + if (pctUndist < colFrame->GetPrefPercent()) { 1.384 + // This can happen with floating-point math. 1.385 + NS_ASSERTION(colFrame->GetPrefPercent() - pctUndist 1.386 + < 0.0001, 1.387 + "widths don't add up"); 1.388 + pctUndist = colFrame->GetPrefPercent(); 1.389 + } 1.390 + nscoord toAdd = AllocateUnassigned(unassignedSpace, 1.391 + colFrame->GetPrefPercent() / pctUndist); 1.392 + colFrame->SetFinalWidth(colFrame->GetFinalWidth() + toAdd); 1.393 + unassignedSpace -= toAdd; 1.394 + pctUndist -= colFrame->GetPrefPercent(); 1.395 + if (pctUndist <= 0.0f) { 1.396 + break; 1.397 + } 1.398 + } 1.399 + NS_ASSERTION(unassignedSpace == 0, "failed to redistribute"); 1.400 + } else { 1.401 + // Distribute equally to the zero-width columns. 1.402 + int32_t colsLeft = colCount; 1.403 + for (int32_t col = 0; col < colCount; ++col) { 1.404 + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); 1.405 + if (!colFrame) { 1.406 + NS_ERROR("column frames out of sync with cell map"); 1.407 + continue; 1.408 + } 1.409 + NS_ASSERTION(colFrame->GetFinalWidth() == 0, "yikes"); 1.410 + nscoord toAdd = AllocateUnassigned(unassignedSpace, 1.411 + 1.0f / float(colsLeft)); 1.412 + colFrame->SetFinalWidth(toAdd); 1.413 + unassignedSpace -= toAdd; 1.414 + --colsLeft; 1.415 + } 1.416 + NS_ASSERTION(unassignedSpace == 0, "failed to redistribute"); 1.417 + } 1.418 + } 1.419 + for (int32_t col = 0; col < colCount; ++col) { 1.420 + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); 1.421 + if (!colFrame) { 1.422 + NS_ERROR("column frames out of sync with cell map"); 1.423 + continue; 1.424 + } 1.425 + if (oldColWidths.ElementAt(col) != colFrame->GetFinalWidth()) { 1.426 + mTableFrame->DidResizeColumns(); 1.427 + break; 1.428 + } 1.429 + } 1.430 +}