layout/tables/FixedTableLayoutStrategy.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

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

mercurial