summaryrefslogtreecommitdiffstats
path: root/layout/tables/FixedTableLayoutStrategy.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/tables/FixedTableLayoutStrategy.cpp')
-rw-r--r--layout/tables/FixedTableLayoutStrategy.cpp402
1 files changed, 402 insertions, 0 deletions
diff --git a/layout/tables/FixedTableLayoutStrategy.cpp b/layout/tables/FixedTableLayoutStrategy.cpp
new file mode 100644
index 0000000000..8d74e3ba12
--- /dev/null
+++ b/layout/tables/FixedTableLayoutStrategy.cpp
@@ -0,0 +1,402 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Algorithms that determine column and table inline sizes used for
+ * CSS2's 'table-layout: fixed'.
+ */
+
+#include "FixedTableLayoutStrategy.h"
+
+#include "nsLayoutUtils.h"
+#include "nsStyleConsts.h"
+#include "nsTableFrame.h"
+#include "nsTableColFrame.h"
+#include "nsTableCellFrame.h"
+#include "WritingModes.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+FixedTableLayoutStrategy::FixedTableLayoutStrategy(nsTableFrame* aTableFrame)
+ : nsITableLayoutStrategy(nsITableLayoutStrategy::Fixed),
+ mTableFrame(aTableFrame) {
+ MarkIntrinsicISizesDirty();
+}
+
+/* virtual */
+FixedTableLayoutStrategy::~FixedTableLayoutStrategy() = default;
+
+/* virtual */
+nscoord FixedTableLayoutStrategy::GetMinISize(gfxContext* aRenderingContext) {
+ DISPLAY_MIN_INLINE_SIZE(mTableFrame, mMinISize);
+ if (mMinISize != NS_INTRINSIC_ISIZE_UNKNOWN) {
+ return mMinISize;
+ }
+
+ // It's theoretically possible to do something much better here that
+ // depends only on the columns and the first row (where we look at
+ // intrinsic inline sizes inside the first row and then reverse the
+ // algorithm to find the narrowest inline size that would hold all of
+ // those intrinsic inline sizes), but it wouldn't be compatible with
+ // other browsers, or with the use of GetMinISize by
+ // nsTableFrame::ComputeSize to determine the inline size of a fixed
+ // layout table, since CSS2.1 says:
+ // The width of the table is then the greater of the value of the
+ // 'width' property for the table element and the sum of the column
+ // widths (plus cell spacing or borders).
+
+ // XXX Should we really ignore 'min-inline-size' and 'max-inline-size'?
+ // XXX Should we really ignore inline sizes on column groups?
+
+ nsTableCellMap* cellMap = mTableFrame->GetCellMap();
+ int32_t colCount = cellMap->GetColCount();
+
+ nscoord result = 0;
+
+ if (colCount > 0) {
+ result += mTableFrame->GetColSpacing(-1, colCount);
+ }
+
+ WritingMode wm = mTableFrame->GetWritingMode();
+ for (int32_t col = 0; col < colCount; ++col) {
+ nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ nscoord spacing = mTableFrame->GetColSpacing(col);
+ const auto* styleISize = &colFrame->StylePosition()->ISize(wm);
+ if (styleISize->ConvertsToLength()) {
+ result += styleISize->ToLength();
+ } else if (styleISize->ConvertsToPercentage()) {
+ // do nothing
+ } else {
+ // The 'table-layout: fixed' algorithm considers only cells in the
+ // first row.
+ bool originates;
+ int32_t colSpan;
+ nsTableCellFrame* cellFrame =
+ cellMap->GetCellInfoAt(0, col, &originates, &colSpan);
+ if (cellFrame) {
+ styleISize = &cellFrame->StylePosition()->ISize(wm);
+ if (styleISize->ConvertsToLength() || styleISize->IsMinContent() ||
+ styleISize->IsMaxContent()) {
+ nscoord cellISize = nsLayoutUtils::IntrinsicForContainer(
+ aRenderingContext, cellFrame, IntrinsicISizeType::MinISize);
+ if (colSpan > 1) {
+ // If a column-spanning cell is in the first row, split up
+ // the space evenly. (XXX This isn't quite right if some of
+ // the columns it's in have specified inline sizes. Should
+ // we care?)
+ cellISize = ((cellISize + spacing) / colSpan) - spacing;
+ }
+ result += cellISize;
+ } else if (styleISize->ConvertsToPercentage()) {
+ if (colSpan > 1) {
+ // XXX Can this force columns to negative inline sizes?
+ result -= spacing * (colSpan - 1);
+ }
+ }
+ // else, for 'auto', '-moz-available', '-moz-fit-content',
+ // and 'calc()' with both lengths and percentages, do nothing
+ }
+ }
+ }
+
+ return (mMinISize = result);
+}
+
+/* virtual */
+nscoord FixedTableLayoutStrategy::GetPrefISize(gfxContext* aRenderingContext,
+ bool aComputingSize) {
+ // It's theoretically possible to do something much better here that
+ // depends only on the columns and the first row (where we look at
+ // intrinsic inline sizes inside the first row and then reverse the
+ // algorithm to find the narrowest inline size that would hold all of
+ // those intrinsic inline sizes), but it wouldn't be compatible with
+ // other browsers.
+ nscoord result = nscoord_MAX;
+ DISPLAY_PREF_INLINE_SIZE(mTableFrame, result);
+ return result;
+}
+
+/* virtual */
+void FixedTableLayoutStrategy::MarkIntrinsicISizesDirty() {
+ mMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
+ mLastCalcISize = nscoord_MIN;
+}
+
+static inline nscoord AllocateUnassigned(nscoord aUnassignedSpace,
+ float aShare) {
+ if (aShare == 1.0f) {
+ // This happens when the numbers we're dividing to get aShare are
+ // equal. We want to return unassignedSpace exactly, even if it
+ // can't be precisely round-tripped through float.
+ return aUnassignedSpace;
+ }
+ return NSToCoordRound(float(aUnassignedSpace) * aShare);
+}
+
+/* virtual */
+void FixedTableLayoutStrategy::ComputeColumnISizes(
+ const ReflowInput& aReflowInput) {
+ nscoord tableISize = aReflowInput.ComputedISize();
+
+ if (mLastCalcISize == tableISize) {
+ return;
+ }
+ mLastCalcISize = tableISize;
+
+ nsTableCellMap* cellMap = mTableFrame->GetCellMap();
+ int32_t colCount = cellMap->GetColCount();
+
+ if (colCount == 0) {
+ // No Columns - nothing to compute
+ return;
+ }
+
+ // border-spacing isn't part of the basis for percentages.
+ tableISize -= mTableFrame->GetColSpacing(-1, colCount);
+
+ // store the old column inline sizes. We might call SetFinalISize
+ // multiple times on the columns, due to this we can't compare at the
+ // last call that the inline size has changed with respect to the last
+ // call to ComputeColumnISizes. In order to overcome this we store the
+ // old values in this array. A single call to SetFinalISize would make
+ // it possible to call GetFinalISize before and to compare when
+ // setting the final inline size.
+ nsTArray<nscoord> oldColISizes;
+
+ // XXX This ignores the 'min-width' and 'max-width' properties
+ // throughout. Then again, that's what the CSS spec says to do.
+
+ // XXX Should we really ignore widths on column groups?
+
+ uint32_t unassignedCount = 0;
+ nscoord unassignedSpace = tableISize;
+ const nscoord unassignedMarker = nscoord_MIN;
+
+ // We use the PrefPercent on the columns to store the percentages
+ // used to compute column inline sizes in case we need to shrink or
+ // expand the columns.
+ float pctTotal = 0.0f;
+
+ // Accumulate the total specified (non-percent) on the columns for
+ // distributing excess inline size to the columns.
+ nscoord specTotal = 0;
+
+ WritingMode wm = mTableFrame->GetWritingMode();
+ for (int32_t col = 0; col < colCount; ++col) {
+ nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ oldColISizes.AppendElement(0);
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ oldColISizes.AppendElement(colFrame->GetFinalISize());
+ colFrame->ResetPrefPercent();
+ const auto* styleISize = &colFrame->StylePosition()->ISize(wm);
+ nscoord colISize;
+ if (styleISize->ConvertsToLength()) {
+ colISize = styleISize->ToLength();
+ specTotal += colISize;
+ } else if (styleISize->ConvertsToPercentage()) {
+ float pct = styleISize->ToPercentage();
+ colISize = NSToCoordFloor(pct * float(tableISize));
+ colFrame->AddPrefPercent(pct);
+ pctTotal += pct;
+ } else {
+ // The 'table-layout: fixed' algorithm considers only cells in the
+ // first row.
+ bool originates;
+ int32_t colSpan;
+ nsTableCellFrame* cellFrame =
+ cellMap->GetCellInfoAt(0, col, &originates, &colSpan);
+ if (cellFrame) {
+ const nsStylePosition* cellStylePos = cellFrame->StylePosition();
+ styleISize = &cellStylePos->ISize(wm);
+ if (styleISize->ConvertsToLength() || styleISize->IsMaxContent() ||
+ styleISize->IsMinContent()) {
+ // XXX This should use real percentage padding
+ // Note that the difference between MinISize and PrefISize
+ // shouldn't matter for any of these values of styleISize; use
+ // MIN_ISIZE for symmetry with GetMinISize above, just in case
+ // there is a difference.
+ colISize = nsLayoutUtils::IntrinsicForContainer(
+ aReflowInput.mRenderingContext, cellFrame,
+ IntrinsicISizeType::MinISize);
+ } else if (styleISize->ConvertsToPercentage()) {
+ // XXX This should use real percentage padding
+ float pct = styleISize->ToPercentage();
+ colISize = NSToCoordFloor(pct * float(tableISize));
+
+ if (cellStylePos->mBoxSizing == StyleBoxSizing::Content) {
+ nsIFrame::IntrinsicSizeOffsetData offsets =
+ cellFrame->IntrinsicISizeOffsets();
+ colISize += offsets.padding + offsets.border;
+ }
+
+ pct /= float(colSpan);
+ colFrame->AddPrefPercent(pct);
+ pctTotal += pct;
+ } else {
+ // 'auto', '-moz-available', '-moz-fit-content', and 'calc()'
+ // with percentages
+ colISize = unassignedMarker;
+ }
+ if (colISize != unassignedMarker) {
+ if (colSpan > 1) {
+ // If a column-spanning cell is in the first row, split up
+ // the space evenly. (XXX This isn't quite right if some of
+ // the columns it's in have specified iSizes. Should we
+ // care?)
+ nscoord spacing = mTableFrame->GetColSpacing(col);
+ colISize = ((colISize + spacing) / colSpan) - spacing;
+ if (colISize < 0) {
+ colISize = 0;
+ }
+ }
+ if (!styleISize->ConvertsToPercentage()) {
+ specTotal += colISize;
+ }
+ }
+ } else {
+ colISize = unassignedMarker;
+ }
+ }
+
+ colFrame->SetFinalISize(colISize);
+
+ if (colISize == unassignedMarker) {
+ ++unassignedCount;
+ } else {
+ unassignedSpace -= colISize;
+ }
+ }
+
+ if (unassignedSpace < 0) {
+ if (pctTotal > 0) {
+ // If the columns took up too much space, reduce those that had
+ // percentage inline sizes. The spec doesn't say to do this, but
+ // we've always done it in the past, and so does WinIE6.
+ nscoord pctUsed = NSToCoordFloor(pctTotal * float(tableISize));
+ nscoord reduce = std::min(pctUsed, -unassignedSpace);
+ float reduceRatio = float(reduce) / pctTotal;
+ for (int32_t col = 0; col < colCount; ++col) {
+ nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ nscoord colISize = colFrame->GetFinalISize();
+ colISize -= NSToCoordFloor(colFrame->GetPrefPercent() * reduceRatio);
+ if (colISize < 0) {
+ colISize = 0;
+ }
+ colFrame->SetFinalISize(colISize);
+ }
+ }
+ unassignedSpace = 0;
+ }
+
+ if (unassignedCount > 0) {
+ // The spec says to distribute the remaining space evenly among
+ // the columns.
+ nscoord toAssign = unassignedSpace / unassignedCount;
+ for (int32_t col = 0; col < colCount; ++col) {
+ nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ if (colFrame->GetFinalISize() == unassignedMarker) {
+ colFrame->SetFinalISize(toAssign);
+ }
+ }
+ } else if (unassignedSpace > 0) {
+ // The spec doesn't say how to distribute the unassigned space.
+ if (specTotal > 0) {
+ // Distribute proportionally to non-percentage columns.
+ nscoord specUndist = specTotal;
+ for (int32_t col = 0; col < colCount; ++col) {
+ nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ if (colFrame->GetPrefPercent() == 0.0f) {
+ NS_ASSERTION(colFrame->GetFinalISize() <= specUndist,
+ "inline sizes don't add up");
+ nscoord toAdd = AllocateUnassigned(
+ unassignedSpace,
+ float(colFrame->GetFinalISize()) / float(specUndist));
+ specUndist -= colFrame->GetFinalISize();
+ colFrame->SetFinalISize(colFrame->GetFinalISize() + toAdd);
+ unassignedSpace -= toAdd;
+ if (specUndist <= 0) {
+ NS_ASSERTION(specUndist == 0, "math should be exact");
+ break;
+ }
+ }
+ }
+ NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
+ } else if (pctTotal > 0) {
+ // Distribute proportionally to percentage columns.
+ float pctUndist = pctTotal;
+ for (int32_t col = 0; col < colCount; ++col) {
+ nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ if (pctUndist < colFrame->GetPrefPercent()) {
+ // This can happen with floating-point math.
+ NS_ASSERTION(colFrame->GetPrefPercent() - pctUndist < 0.0001,
+ "inline sizes don't add up");
+ pctUndist = colFrame->GetPrefPercent();
+ }
+ nscoord toAdd = AllocateUnassigned(
+ unassignedSpace, colFrame->GetPrefPercent() / pctUndist);
+ colFrame->SetFinalISize(colFrame->GetFinalISize() + toAdd);
+ unassignedSpace -= toAdd;
+ pctUndist -= colFrame->GetPrefPercent();
+ if (pctUndist <= 0.0f) {
+ break;
+ }
+ }
+ NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
+ } else {
+ // Distribute equally to the zero-iSize columns.
+ int32_t colsRemaining = colCount;
+ for (int32_t col = 0; col < colCount; ++col) {
+ nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ NS_ASSERTION(colFrame->GetFinalISize() == 0, "yikes");
+ nscoord toAdd =
+ AllocateUnassigned(unassignedSpace, 1.0f / float(colsRemaining));
+ colFrame->SetFinalISize(toAdd);
+ unassignedSpace -= toAdd;
+ --colsRemaining;
+ }
+ NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
+ }
+ }
+ for (int32_t col = 0; col < colCount; ++col) {
+ nsTableColFrame* colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ if (oldColISizes.ElementAt(col) != colFrame->GetFinalISize()) {
+ mTableFrame->DidResizeColumns();
+ break;
+ }
+ }
+}