/*
 * Copyright (C) 2019 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "TableFormattingContext.h"

#if ENABLE(LAYOUT_FORMATTING_CONTEXT)

#include "BlockFormattingState.h"
#include "FloatingState.h"
#include "InlineFormattingState.h"
#include "LayoutBox.h"
#include "LayoutBoxGeometry.h"
#include "LayoutChildIterator.h"
#include "LayoutContext.h"
#include "LayoutInitialContainingBlock.h"
#include "TableFormattingConstraints.h"
#include "TableFormattingState.h"
#include <wtf/IsoMallocInlines.h>

namespace WebCore {
namespace Layout {

WTF_MAKE_ISO_ALLOCATED_IMPL(TableFormattingContext);

// https://www.w3.org/TR/css-tables-3/#table-layout-algorithm
TableFormattingContext::TableFormattingContext(const ContainerBox& formattingContextRoot, TableFormattingState& formattingState)
    : FormattingContext(formattingContextRoot, formattingState)
    , m_tableFormattingGeometry(*this)
    , m_tableFormattingQuirks(*this)
{
}

void TableFormattingContext::layoutInFlowContent(const ConstraintsForInFlowContent& constraints)
{
    auto availableHorizontalSpace = constraints.horizontal().logicalWidth;
    auto availableVerticalSpace = downcast<ConstraintsForTableContent>(constraints).availableVerticalSpaceForContent();
    // 1. Compute width and height for the grid.
    computeAndDistributeExtraSpace(availableHorizontalSpace, availableVerticalSpace);
    // 2. Finalize cells.
    setUsedGeometryForCells(availableHorizontalSpace, availableVerticalSpace);
    // 3. Finalize rows.
    setUsedGeometryForRows(availableHorizontalSpace);
    // 4. Finalize sections.
    setUsedGeometryForSections(constraints);
}

LayoutUnit TableFormattingContext::usedContentHeight() const
{
    // Table has to have some section content, at least one <tbody>.
    auto top = BoxGeometry::marginBoxRect(geometryForBox(*root().firstInFlowChild())).top();
    auto bottom = BoxGeometry::marginBoxRect(geometryForBox(*root().lastInFlowChild())).bottom();
    return bottom - top;
}

void TableFormattingContext::setUsedGeometryForCells(LayoutUnit availableHorizontalSpace, std::optional<LayoutUnit> availableVerticalSpace)
{
    auto& grid = formattingState().tableGrid();
    auto& columnList = grid.columns().list();
    auto& rowList = grid.rows().list();
    auto& formattingGeometry = this->formattingGeometry();
    // Final table cell layout. At this point all percentage values can be resolved.
    auto sectionOffset = LayoutUnit { };
    auto* currentSection = &rowList.first().box().parent();
    for (auto& cell : grid.cells()) {
        auto& cellBox = cell->box();
        auto& cellBoxGeometry = formattingState().boxGeometry(cellBox);
        auto& section = rowList[cell->startRow()].box().parent();
        if (&section != currentSection) {
            currentSection = &section;
            // While the grid is a continuous flow of rows, in the display tree they are relative to their sections.
            sectionOffset = rowList[cell->startRow()].logicalTop();
        }
        // Internal table elements do not have margins.
        cellBoxGeometry.setHorizontalMargin({ });
        cellBoxGeometry.setVerticalMargin({ });

        cellBoxGeometry.setBorder(formattingGeometry.computedCellBorder(*cell));
        cellBoxGeometry.setPadding(formattingGeometry.computedPadding(cellBox, availableHorizontalSpace));
        cellBoxGeometry.setLogicalTop(rowList[cell->startRow()].logicalTop() - sectionOffset);
        cellBoxGeometry.setLogicalLeft(columnList[cell->startColumn()].usedLogicalLeft());
        cellBoxGeometry.setContentBoxWidth(formattingGeometry.horizontalSpaceForCellContent(*cell));

        if (cellBox.hasInFlowOrFloatingChild()) {
            // FIXME: This should probably be part of the invalidation state to indicate when we re-layout the cell multiple times as part of the multi-pass table algorithm.
            auto& floatingStateForCellContent = layoutState().ensureBlockFormattingState(cellBox).floatingState();
            floatingStateForCellContent.clear();
            LayoutContext::createFormattingContext(cellBox, layoutState())->layoutInFlowContent(formattingGeometry.constraintsForInFlowContent(cellBox));
        }
        cellBoxGeometry.setContentBoxHeight(formattingGeometry.verticalSpaceForCellContent(*cell, availableVerticalSpace));

        auto computeIntrinsicVerticalPaddingForCell = [&] {
            auto cellLogicalHeight = rowList[cell->startRow()].logicalHeight();
            for (size_t rowIndex = cell->startRow() + 1; rowIndex < cell->endRow(); ++rowIndex)
                cellLogicalHeight += rowList[rowIndex].logicalHeight();
            cellLogicalHeight += (cell->rowSpan() - 1) * grid.verticalSpacing();
            // Intrinsic padding is the extra padding for the cell box when it is shorter than the row. Cell boxes have to
            // fill the available vertical space
            // e.g <td height=100px></td><td height=1px></td>
            // the second <td> ends up being 100px tall too with the extra intrinsic padding.

            // FIXME: Find out if it is ok to use the regular padding here to align the content box inside a tall cell or we need to
            // use some kind of intrinsic padding similar to RenderTableCell.
            auto paddingTop = valueOrDefault(cellBoxGeometry.paddingBefore());
            auto paddingBottom = valueOrDefault(cellBoxGeometry.paddingAfter());
            auto intrinsicPaddingTop = LayoutUnit { };
            auto intrinsicPaddingBottom = LayoutUnit { };

            switch (cellBox.style().verticalAlign()) {
            case VerticalAlign::Middle: {
                auto intrinsicVerticalPadding = std::max(0_lu, cellLogicalHeight - cellBoxGeometry.verticalMarginBorderAndPadding() - cellBoxGeometry.contentBoxHeight());
                intrinsicPaddingTop = intrinsicVerticalPadding / 2;
                intrinsicPaddingBottom = intrinsicVerticalPadding / 2;
                break;
            }
            case VerticalAlign::Baseline: {
                auto rowBaseline = LayoutUnit { rowList[cell->startRow()].baseline() };
                auto cellBaseline = LayoutUnit { cell->baseline() };
                intrinsicPaddingTop = std::max(0_lu, rowBaseline - cellBaseline - cellBoxGeometry.borderBefore());
                intrinsicPaddingBottom = std::max(0_lu, cellLogicalHeight - cellBoxGeometry.verticalMarginBorderAndPadding() - intrinsicPaddingTop - cellBoxGeometry.contentBoxHeight());
                break;
            }
            default:
                ASSERT_NOT_IMPLEMENTED_YET();
                break;
            }
            if (intrinsicPaddingTop && cellBox.hasInFlowOrFloatingChild()) {
                auto adjustCellContentWithInstrinsicPaddingBefore = [&] {
                    // Child boxes (and runs) are always in the coordinate system of the containing block's border box.
                    // The content box (where the child content lives) is inside the padding box, which is inside the border box.
                    // In order to compute the child box top/left position, we need to know both the padding and the border offsets.
                    // Normally by the time we start positioning the child content, we already have computed borders and padding for the containing block.
                    // This is different with table cells where the final padding offset depends on the content height as we use
                    // the padding box to vertically align the table cell content.
                    auto& formattingState = layoutState().formattingStateForFormattingContext(cellBox);
                    for (auto* child = cellBox.firstInFlowOrFloatingChild(); child; child = child->nextInFlowOrFloatingSibling()) {
                        if (child->isInlineTextBox())
                            continue;
                        formattingState.boxGeometry(*child).moveVertically(intrinsicPaddingTop);
                    }
                    if (cellBox.establishesInlineFormattingContext()) {
                        auto& inlineFormattingStatee = layoutState().formattingStateForInlineFormattingContext(cellBox);
                        for (auto& box : inlineFormattingStatee.boxes())
                            box.moveVertically(intrinsicPaddingTop);
                        for (auto& line : inlineFormattingStatee.lines())
                            line.moveVertically(intrinsicPaddingTop);
                    }
                };
                adjustCellContentWithInstrinsicPaddingBefore();
            }
            cellBoxGeometry.setVerticalPadding({ paddingTop + intrinsicPaddingTop, paddingBottom + intrinsicPaddingBottom });
        };
        computeIntrinsicVerticalPaddingForCell();
    }
}

void TableFormattingContext::setUsedGeometryForRows(LayoutUnit availableHorizontalSpace)
{
    auto& grid = formattingState().tableGrid();
    auto& rows = grid.rows().list();

    auto rowLogicalTop = grid.verticalSpacing();
    const ContainerBox* previousRow = nullptr;
    for (size_t rowIndex = 0; rowIndex < rows.size(); ++rowIndex) {
        auto& row = rows[rowIndex];
        auto& rowBox = row.box();
        auto& rowBoxGeometry = formattingState().boxGeometry(rowBox);

        rowBoxGeometry.setPadding(formattingGeometry().computedPadding(rowBox, availableHorizontalSpace));
        // Internal table elements do not have margins.
        rowBoxGeometry.setHorizontalMargin({ });
        rowBoxGeometry.setVerticalMargin({ });

        auto computedRowBorder = [&] {
            auto border = formattingGeometry().computedBorder(rowBox);
            if (!grid.collapsedBorder())
                return border;
            // Border collapsing delegates borders to table/cells.
            border.horizontal = { };
            if (!rowIndex)
                border.vertical.top = { };
            if (rowIndex == rows.size() - 1)
                border.vertical.bottom = { };
            return border;
        }();
        if (computedRowBorder.height() > row.logicalHeight()) {
            // FIXME: This is an odd quirk when the row border overflows the row.
            // We don't paint row borders so it does not matter too much, but if we don't
            // set this fake border value, than we either end up with a negative content box
            // or with a wide frame box.
            // If it happens to cause issues in the display tree, we could also consider
            // a special frame box override, where padding box + border != frame box.
            computedRowBorder.vertical.top = { };
            computedRowBorder.vertical.bottom = { };
        }
        rowBoxGeometry.setContentBoxHeight(row.logicalHeight() - computedRowBorder.height());

        auto rowLogicalWidth = grid.columns().logicalWidth() + 2 * grid.horizontalSpacing();
        if (computedRowBorder.width() > rowLogicalWidth) {
            // See comment above.
            computedRowBorder.horizontal.left = { };
            computedRowBorder.horizontal.right = { };
        }
        rowBoxGeometry.setContentBoxWidth(rowLogicalWidth - computedRowBorder.width());
        rowBoxGeometry.setBorder(computedRowBorder);

        if (previousRow && &previousRow->parent() != &rowBox.parent()) {
            // This row is in a different section.
            rowLogicalTop = { };
        }
        rowBoxGeometry.setLogicalTop(rowLogicalTop);
        rowBoxGeometry.setLogicalLeft({ });

        rowLogicalTop += row.logicalHeight() + grid.verticalSpacing();
        previousRow = &rowBox;
    }

    auto& columns = grid.columns();
    Vector<InlineLayoutUnit> rowBaselines(rows.size(), 0);
    // Now that cells are laid out, let's compute the row baselines.
    for (size_t rowIndex = 0; rowIndex < rows.size(); ++rowIndex) {
        for (size_t columnIndex = 0; columnIndex < columns.size(); ++columnIndex) {
            auto& slot = *grid.slot({ columnIndex, rowIndex });
            if (slot.isRowSpanned())
                continue;
            if (slot.hasRowSpan())
                continue;
            auto& cell = slot.cell();
            rowBaselines[rowIndex] = std::max(rowBaselines[rowIndex], cell.baseline());
        }
    }
    for (size_t rowIndex = 0; rowIndex < rows.size(); ++rowIndex)
        rows[rowIndex].setBaseline(rowBaselines[rowIndex]);
}

void TableFormattingContext::setUsedGeometryForSections(const ConstraintsForInFlowContent& constraints)
{
    auto& grid = formattingState().tableGrid();
    auto& tableBox = root();
    auto sectionWidth = grid.columns().logicalWidth() + 2 * grid.horizontalSpacing();
    auto logicalTop = constraints.logicalTop();
    auto verticalSpacing = grid.verticalSpacing();
    auto paddingBefore = std::optional<LayoutUnit> { verticalSpacing };
    auto paddingAfter = verticalSpacing;
    for (auto& sectionBox : childrenOfType<ContainerBox>(tableBox)) {
        auto& sectionBoxGeometry = formattingState().boxGeometry(sectionBox);
        // Section borders are either collapsed or ignored.
        sectionBoxGeometry.setBorder({ });
        // Use fake vertical padding to space out the sections.
        sectionBoxGeometry.setPadding(Edges { { }, { paddingBefore.value_or(0_lu), paddingAfter } });
        paddingBefore = std::nullopt;
        // Internal table elements do not have margins.
        sectionBoxGeometry.setHorizontalMargin({ });
        sectionBoxGeometry.setVerticalMargin({ });

        sectionBoxGeometry.setContentBoxWidth(sectionWidth);
        auto sectionContentHeight = LayoutUnit { };
        size_t rowCount = 0;
        for (auto& rowBox : childrenOfType<ContainerBox>(sectionBox)) {
            sectionContentHeight += geometryForBox(rowBox).borderBoxHeight();
            ++rowCount;
        }
        sectionContentHeight += verticalSpacing * (rowCount - 1);
        sectionBoxGeometry.setContentBoxHeight(sectionContentHeight);
        sectionBoxGeometry.setLogicalLeft(constraints.horizontal().logicalLeft);
        sectionBoxGeometry.setLogicalTop(logicalTop);

        logicalTop += sectionBoxGeometry.borderBoxHeight();
    }
}

IntrinsicWidthConstraints TableFormattingContext::computedIntrinsicWidthConstraints()
{
    ASSERT(!root().isSizeContainmentBox());
    // Tables have a slightly different concept of shrink to fit. It's really only different with non-auto "width" values, where
    // a generic shrink-to fit block level box like a float box would be just sized to the computed value of "width", tables
    // can actually be stretched way over.
    auto& grid = formattingState().tableGrid();
    if (auto computedWidthConstraints = grid.widthConstraints())
        return *computedWidthConstraints;

    // Compute the minimum/maximum width of each column.
    auto computedWidthConstraints = computedPreferredWidthForColumns();
    grid.setWidthConstraints(computedWidthConstraints);
    return computedWidthConstraints;
}

IntrinsicWidthConstraints TableFormattingContext::computedPreferredWidthForColumns()
{
    auto& formattingState = this->formattingState();
    auto& grid = formattingState.tableGrid();
    ASSERT(!grid.widthConstraints());

    // Column preferred width computation as follows:
    // 1. Collect fixed column widths set by <colgroup>'s and <col>s
    // 2. Collect each cells' width constraints and adjust fixed width column values.
    // 3. Find the min/max width for each columns using the cell constraints and the <col> fixed widths but ignore column spans.
    // 4. Distribute column spanning cells min/max widths.
    // 5. Add them all up and return the computed min/max widths.
    // 2. Collect the fixed width <col>s.
    auto& columnList = grid.columns().list();
    auto& formattingGeometry = this->formattingGeometry();
    auto collectColsFixedWidth = [&] {
        for (auto& column : columnList) {
            auto fixedWidth = [&] () -> std::optional<LayoutUnit> {
                auto* columnBox = column.box();
                if (!columnBox) {
                    // Anonymous columns don't have associated layout boxes and can't have fixed col size.
                    return { };
                }
                if (auto width = columnBox->columnWidth())
                    return width;
                return formattingGeometry.computedColumnWidth(*columnBox);
            }();
        if (fixedWidth)
            column.setComputedLogicalWidth({ *fixedWidth, LengthType::Fixed });
        }
    };
    collectColsFixedWidth();

    auto hasColumnWithPercentWidth = false;
    auto hasColumnWithFixedWidth = false;
    Vector<std::optional<LayoutUnit>> maximumFixedColumnWidths(columnList.size());
    Vector<std::optional<float>> maximumPercentColumnWidths(columnList.size());

    auto collectCellsIntrinsicWidthConstraints = [&] {
        for (auto& cell : grid.cells()) {
            auto& cellBox = cell->box();
            ASSERT(cellBox.establishesBlockFormattingContext());

            auto intrinsicWidth = formattingState.intrinsicWidthConstraintsForBox(cellBox);
            if (!intrinsicWidth) {
                intrinsicWidth = formattingGeometry.intrinsicWidthConstraintsForCellContent(*cell);
                formattingState.setIntrinsicWidthConstraintsForBox(cellBox, *intrinsicWidth);
            }
            auto cellPosition = cell->position();
            auto& cellStyle = cellBox.style();
            // Expand it with border and padding.
            auto horizontalBorderAndPaddingWidth = formattingGeometry.computedCellBorder(*cell).width()
                + formattingGeometry.fixedValue(cellStyle.paddingLeft()).value_or(0)
                + formattingGeometry.fixedValue(cellStyle.paddingRight()).value_or(0);
            intrinsicWidth->expand(horizontalBorderAndPaddingWidth);
            // Spanner cells put their intrinsic widths on the initial slots.
            grid.slot(cellPosition)->setWidthConstraints(*intrinsicWidth);

            auto cellLogicalWidth = cellStyle.logicalWidth();
            auto columnIndex = cellPosition.column;
            switch (cellLogicalWidth.type()) {
            case LengthType::Fixed: {
                auto fixedWidth = LayoutUnit { cellLogicalWidth.value() } + horizontalBorderAndPaddingWidth;
                maximumFixedColumnWidths[columnIndex] = std::max(maximumFixedColumnWidths[columnIndex].value_or(0_lu), fixedWidth);
                hasColumnWithFixedWidth = true;
                break;
            }
            case LengthType::Percent: {
                maximumPercentColumnWidths[columnIndex] = std::max(maximumPercentColumnWidths[columnIndex].value_or(0.f), cellLogicalWidth.percent());
                hasColumnWithPercentWidth = true;
                break;
            }
            case LengthType::Relative:
                ASSERT_NOT_IMPLEMENTED_YET();
                break;
            default:
                break;
            }
        }
    };
    collectCellsIntrinsicWidthConstraints();

    Vector<IntrinsicWidthConstraints> columnIntrinsicWidths(columnList.size());
    Vector<SlotPosition> spanningCellPositionList;
    size_t numberOfActualColumns = 0;
    auto computeColumnsIntrinsicWidthConstraints = [&] {
        // 3. Collect he min/max width for each column but ignore column spans for now.
        for (size_t columnIndex = 0; columnIndex < columnList.size(); ++columnIndex) {
            auto columnHasNonSpannedCell = false;
            for (size_t rowIndex = 0; rowIndex < grid.rows().size(); ++rowIndex) {
                auto& slot = *grid.slot({ columnIndex, rowIndex });
                if (slot.isColumnSpanned())
                    continue;
                columnHasNonSpannedCell = true;
                if (slot.hasColumnSpan()) {
                    spanningCellPositionList.append({ columnIndex, rowIndex });
                    continue;
                }
                auto widthConstraints = slot.widthConstraints();
                if (auto fixedColumnWidth = maximumFixedColumnWidths[columnIndex])
                    widthConstraints.maximum = std::max(*fixedColumnWidth, widthConstraints.minimum);

                columnIntrinsicWidths[columnIndex].minimum = std::max(widthConstraints.minimum, columnIntrinsicWidths[columnIndex].minimum);
                columnIntrinsicWidths[columnIndex].maximum = std::max(widthConstraints.maximum, columnIntrinsicWidths[columnIndex].maximum);
            }
            if (columnHasNonSpannedCell)
                ++numberOfActualColumns;
        }
    };
    computeColumnsIntrinsicWidthConstraints();

    auto resolveSpanningCells = [&] {
        // 4. Distribute the spanning min/max widths.
        for (auto spanningCellPosition : spanningCellPositionList) {
            auto& slot = *grid.slot(spanningCellPosition);
            auto& cell = slot.cell();
            ASSERT(slot.hasColumnSpan());
            auto widthConstraintsToDistribute = slot.widthConstraints();
            for (size_t columnSpanIndex = cell.startColumn(); columnSpanIndex < cell.endColumn(); ++columnSpanIndex)
                widthConstraintsToDistribute -= columnIntrinsicWidths[columnSpanIndex];
            // <table style="border-spacing: 50px"><tr><td colspan=2>long long text</td></tr><tr><td>lo</td><td>xt</td><tr></table>
            // [long long text]
            // [lo]        [xt]
            // While it looks like the spanning cell has to distribute all its spanning width, the border-spacing takes most of the space and
            // no distribution is needed at all.
            widthConstraintsToDistribute -= (cell.columnSpan() - 1) * grid.horizontalSpacing();
            // FIXME: Check if fixed width columns should be skipped here.
            widthConstraintsToDistribute.minimum = std::max(LayoutUnit { }, widthConstraintsToDistribute.minimum / cell.columnSpan());
            widthConstraintsToDistribute.maximum = std::max(LayoutUnit { }, widthConstraintsToDistribute.maximum / cell.columnSpan());
            if (widthConstraintsToDistribute.minimum || widthConstraintsToDistribute.maximum) {
                for (size_t columnSpanIndex = cell.startColumn(); columnSpanIndex < cell.endColumn(); ++columnSpanIndex)
                    columnIntrinsicWidths[columnSpanIndex] += widthConstraintsToDistribute;
            }
        }
    };
    resolveSpanningCells();

    // 5. The table min/max widths is just the accumulated column constraints with the percent adjustment.
    auto tableWidthConstraints = IntrinsicWidthConstraints { };
    for (auto& columnIntrinsicWidth : columnIntrinsicWidths)
        tableWidthConstraints += columnIntrinsicWidth;

    auto adjustColumnsWithPercentAndFixedWidthValues = [&] {
        // 6. Adjust the table max width with the percent column values if applicable.
        if (!hasColumnWithFixedWidth && !hasColumnWithPercentWidth)
            return;

        if (hasColumnWithFixedWidth && !hasColumnWithPercentWidth) {
            for (size_t columnIndex = 0; columnIndex < columnList.size(); ++columnIndex) {
                if (auto fixedWidth = maximumFixedColumnWidths[columnIndex])
                    columnList[columnIndex].setComputedLogicalWidth({ *fixedWidth, LengthType::Fixed });
            }
            return;
        } 

        auto remainingPercent = 100.0f;
        auto percentMaximumWidth = LayoutUnit { };
        auto nonPercentColumnsWidth = LayoutUnit { };
        // Resolve the percent values as follows
        // - the percent value is resolved against the column maximum width (fixed or content based) as if the max value represented the percentage value
        //   e.g 50% with the maximum width of 100px produces a resolved width of 200px for the column.
        // - find the largest resolved value across the columns and used that as the maximum width for the percent based columns.
        // - Compute the non-percent based columns width by using the remaining percent value (e.g 50% and 10% columns would leave 40% for the rest of the columns)
        for (size_t columnIndex = 0; columnIndex < columnList.size(); ++columnIndex) {
            auto nonPercentColumnWidth = columnIntrinsicWidths[columnIndex].maximum;
            if (auto fixedWidth = maximumFixedColumnWidths[columnIndex]) {
                columnList[columnIndex].setComputedLogicalWidth({ *fixedWidth, LengthType::Fixed });
                nonPercentColumnWidth = std::max(nonPercentColumnWidth, *fixedWidth);
            }
            if (!maximumPercentColumnWidths[columnIndex]) {
                nonPercentColumnsWidth += nonPercentColumnWidth;
                continue;
            }
            auto percent = std::min(*maximumPercentColumnWidths[columnIndex], remainingPercent);
            columnList[columnIndex].setComputedLogicalWidth({ percent, LengthType::Percent });
            percentMaximumWidth = std::max(percentMaximumWidth, LayoutUnit { nonPercentColumnWidth * 100.0f / percent });
            remainingPercent -= percent;
        }
        ASSERT(remainingPercent >= 0.f);
        auto adjustedMaximumWidth = percentMaximumWidth;
        if (remainingPercent)
            adjustedMaximumWidth = std::max(adjustedMaximumWidth, LayoutUnit { nonPercentColumnsWidth * 100.0f / remainingPercent });
        else {
            // When the table has percent width column(s) and they add up to (or over) 100%, the maximum width is computed to
            // only constrained by the available horizontal width.
            // This is a very odd transition of going from 99.9% to 100%, where 99.9% computes normally (see above)
            // but as soon as we hit the 100% mark, the table suddenly stretches all the way to the horizontal available space.
            // It may very well be an ancient bug we need to support (it maps to the epsilon value in AutoTableLayout::computeIntrinsicLogicalWidths which is to avoid division by zero).
            adjustedMaximumWidth = LayoutUnit::max();
        }
        tableWidthConstraints.maximum = std::max(tableWidthConstraints.maximum, adjustedMaximumWidth);
    };
    adjustColumnsWithPercentAndFixedWidthValues();

    // Expand the preferred width with leading and trailing cell spacing (note that column spanners count as one cell).
    tableWidthConstraints += (numberOfActualColumns + 1) * grid.horizontalSpacing();
    return tableWidthConstraints;
}

void TableFormattingContext::computeAndDistributeExtraSpace(LayoutUnit availableHorizontalSpace, std::optional<LayoutUnit> availableVerticalSpace)
{
    // Compute and balance the column and row spaces.
    auto& grid = formattingState().tableGrid();
    auto& columns = grid.columns().list();
    auto tableLayout = this->tableLayout();

    // Columns first.
    auto distributedHorizontalSpaces = tableLayout.distributedHorizontalSpace(availableHorizontalSpace);
    ASSERT(distributedHorizontalSpaces.size() == columns.size());
    auto columnLogicalLeft = grid.horizontalSpacing();
    for (size_t columnIndex = 0; columnIndex < columns.size(); ++columnIndex) {
        auto& column = columns[columnIndex];
        column.setUsedLogicalLeft(columnLogicalLeft);
        column.setUsedLogicalWidth(distributedHorizontalSpaces[columnIndex]);
        columnLogicalLeft += distributedHorizontalSpaces[columnIndex] + grid.horizontalSpacing();
    }

    auto& formattingGeometry = this->formattingGeometry();
    // Rows second.
    auto& rows = grid.rows().list();
    for (size_t rowIndex = 0; rowIndex < rows.size(); ++rowIndex) {
        for (size_t columnIndex = 0; columnIndex < columns.size(); ++columnIndex) {
            auto& slot = *grid.slot({ columnIndex, rowIndex });
            if (slot.isRowSpanned())
                continue;
            auto layoutCellContent = [&](auto& cell) {
                auto& cellBox = cell.box();
                auto& cellBoxGeometry = formattingState().boxGeometry(cellBox);
                cellBoxGeometry.setBorder(formattingGeometry.computedCellBorder(cell));
                cellBoxGeometry.setPadding(formattingGeometry.computedPadding(cellBox, availableHorizontalSpace));
                cellBoxGeometry.setContentBoxWidth(formattingGeometry.horizontalSpaceForCellContent(cell));

                if (cellBox.hasInFlowOrFloatingChild())
                    LayoutContext::createFormattingContext(cellBox, layoutState())->layoutInFlowContent(formattingGeometry.constraintsForInFlowContent(cellBox));
                cellBoxGeometry.setContentBoxHeight(formattingGeometry.verticalSpaceForCellContent(cell, availableVerticalSpace));
            };
            layoutCellContent(slot.cell());
            if (slot.hasRowSpan())
                continue;
            // The minimum height of a row (without spanning-related height distribution) is defined as the height of an hypothetical
            // linebox containing the cells originating in the row.
            auto& cell = slot.cell();
            cell.setBaseline(formattingGeometry.usedBaselineForCell(cell.box()));
        }
    }

    auto distributedVerticalSpaces = tableLayout.distributedVerticalSpace(availableVerticalSpace);
    ASSERT(distributedVerticalSpaces.size() == rows.size());
    auto rowLogicalTop = grid.verticalSpacing();
    for (size_t rowIndex = 0; rowIndex < rows.size(); ++rowIndex) {
        auto& row = rows[rowIndex];
        row.setLogicalHeight(distributedVerticalSpaces[rowIndex]);
        row.setLogicalTop(rowLogicalTop);
        rowLogicalTop += distributedVerticalSpaces[rowIndex] + grid.verticalSpacing();
    }
}

}
}

#endif
