All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.itextpdf.layout.renderer.TableWidths Maven / Gradle / Ivy

There is a newer version: 9.0.0
Show newest version
/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2023 Apryse Group NV
    Authors: Apryse Software.

    This program is offered under a commercial and under the AGPL license.
    For commercial licensing, contact us at https://itextpdf.com/sales.  For AGPL licensing, see below.

    AGPL licensing:
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see .
 */
package com.itextpdf.layout.renderer;

import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.io.util.ArrayUtil;
import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.layout.borders.Border;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.minmaxwidth.MinMaxWidth;
import com.itextpdf.layout.minmaxwidth.MinMaxWidthUtils;
import com.itextpdf.layout.properties.BorderCollapsePropertyValue;
import com.itextpdf.layout.properties.Property;
import com.itextpdf.layout.properties.UnitValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

final class TableWidths {

    private final TableRenderer tableRenderer;
    private final int numberOfColumns;
    private final float rightBorderMaxWidth;
    private final float leftBorderMaxWidth;
    private final ColumnWidthData[] widths;
    private final float horizontalBorderSpacing;
    private List cells;

    private float tableWidth;
    private boolean fixedTableWidth;
    private boolean fixedTableLayout = false;
    private float layoutMinWidth;
    private float tableMinWidth;
    private float tableMaxWidth;

    TableWidths(TableRenderer tableRenderer, float availableWidth, boolean calculateTableMaxWidth,
                float rightBorderMaxWidth, float leftBorderMaxWidth) {
        this.tableRenderer = tableRenderer;
        this.numberOfColumns = ((Table) tableRenderer.getModelElement()).getNumberOfColumns();
        this.widths = new ColumnWidthData[numberOfColumns];
        this.rightBorderMaxWidth = rightBorderMaxWidth;
        this.leftBorderMaxWidth = leftBorderMaxWidth;
        if (tableRenderer.bordersHandler instanceof SeparatedTableBorders) {
            Float horizontalSpacing = tableRenderer.getPropertyAsFloat(Property.HORIZONTAL_BORDER_SPACING);
            horizontalBorderSpacing = null == horizontalSpacing ? 0 : (float) horizontalSpacing;
        } else {
            horizontalBorderSpacing = 0;
        }
        calculateTableWidth(availableWidth, calculateTableMaxWidth);
    }

    boolean hasFixedLayout() {
        return fixedTableLayout;
    }

    float[] layout() {
        if (hasFixedLayout()) {
            return fixedLayout();
        } else {
            return autoLayout();
        }
    }

    float getMinWidth() {
        return layoutMinWidth;
    }

    float[] autoLayout() {
        assert tableRenderer.getTable().isComplete();
        fillAndSortCells();
        calculateMinMaxWidths();

        float minSum = 0;
        for (ColumnWidthData width : widths) {
            minSum += width.min;
        }

        for (CellInfo cell : cells) {
            processCell(cell);
        }

        processColumns();

        recalculate(minSum);

        return extractWidths();
    }

   List autoLayoutCustom() {
        assert tableRenderer.getTable().isComplete();
        fillAndSortCells();
        calculateMinMaxWidths();
        return cells;
    }

    void processCell(CellInfo cell) {

        // For automatic layout algorithm percents have higher priority
        // value must be > 0, while for fixed layout >= 0
        UnitValue cellWidth = getCellWidth(cell.getCell(), false);
        if (cellWidth != null) {
            assert cellWidth.getValue() > 0;
            if (cellWidth.isPercentValue()) {

                //cellWidth has percent value
                if (cell.getColspan() == 1) {
                    widths[cell.getCol()].setPercents(cellWidth.getValue());
                } else {
                    int pointColumns = 0;
                    float percentSum = 0;
                    for (int i = cell.getCol(); i < cell.getCol() + cell.getColspan(); i++) {
                        if (!widths[i].isPercent) {
                            pointColumns++;
                        } else {
                            percentSum += widths[i].width;
                        }
                    }
                    float percentAddition = cellWidth.getValue() - percentSum;
                    if (percentAddition > 0) {
                        if (pointColumns == 0) {

                            //ok, add percents to each column
                            for (int i = cell.getCol(); i < cell.getCol() + cell.getColspan(); i++) {
                                widths[i].addPercents(percentAddition / cell.getColspan());
                            }
                        } else {

                            // set percent only to cells without one
                            for (int i = cell.getCol(); i < cell.getCol() + cell.getColspan(); i++) {
                                if (!widths[i].isPercent) {
                                    widths[i].setPercents(percentAddition / pointColumns);
                                }
                            }
                        }
                    }
                }
            } else {

                //cellWidth has point value
                if (cell.getColspan() == 1) {
                    if (!widths[cell.getCol()].isPercent) {
                        if (widths[cell.getCol()].min <= cellWidth.getValue()) {
                            widths[cell.getCol()].setPoints(cellWidth.getValue()).setFixed(true);
                        } else {
                            widths[cell.getCol()].setPoints(widths[cell.getCol()].min);
                        }
                    }
                } else {
                    processCellsRemainWidth(cell,cellWidth);
                }
            }
        } else if (widths[cell.getCol()].isFlexible()) {

            //if there is no information, try to set max width
            int flexibleCols = 0;
            float remainWidth = 0;
            for (int i = cell.getCol(); i < cell.getCol() + cell.getColspan(); i++) {
                if (widths[i].isFlexible()) {
                    remainWidth += widths[i].max - widths[i].width;
                    flexibleCols++;
                }
            }
            if (remainWidth > 0) {
                // flexibleCols > 0 too
                
                for (int i = cell.getCol(); i < cell.getCol() + cell.getColspan(); i++) {
                    if (widths[i].isFlexible()) {
                        widths[i].addPoints(remainWidth / flexibleCols);
                    }
                }
            }
        }
    }

    void processColumns() {
        for (int i = 0; i < numberOfColumns; i++) {
            UnitValue colWidth = getTable().getColumnWidth(i);
            if (colWidth != null && colWidth.getValue() > 0) {
                if (colWidth.isPercentValue()) {
                    if (!widths[i].isPercent) {
                        if (widths[i].isFixed && widths[i].width > widths[i].min) {
                            widths[i].max = widths[i].width;
                        }
                        widths[i].setPercents(colWidth.getValue());
                    }
                } else if (!widths[i].isPercent && colWidth.getValue() >= widths[i].min) {
                    if (widths[i].isFixed) {
                        widths[i].setPoints(colWidth.getValue());
                    } else {
                        widths[i].resetPoints(colWidth.getValue()).setFixed(true);
                    }
                }
            }
        }
    }

    void recalculate(float minSum) {
        if (tableWidth - minSum < 0) {
            for (int i = 0; i < numberOfColumns; i++) {
                widths[i].finalWidth = widths[i].min;
            }
        } else {
            float sumOfPercents = 0;
            // minTableWidth include only non percent columns.
            float minTableWidth = 0;
            float totalNonPercent = 0;

            // validate sumOfPercents, last columns will be set min width, if sum > 100.
            for (int i = 0; i < widths.length; i++) {
                if (widths[i].isPercent) {
                    if (sumOfPercents < 100 && sumOfPercents + widths[i].width > 100) {
                        widths[i].width = 100 - sumOfPercents;
                        sumOfPercents += widths[i].width;
                        warn100percent();
                    } else if (sumOfPercents >= 100) {
                        widths[i].resetPoints(widths[i].min);
                        minTableWidth += widths[i].min;
                        warn100percent();
                    } else {
                        sumOfPercents += widths[i].width;
                    }
                } else {
                    minTableWidth += widths[i].min;
                    totalNonPercent += widths[i].width;
                }
            }
            assert sumOfPercents <= 100;

            boolean toBalance = true;
            if (!fixedTableWidth) {
                float tableWidthBasedOnPercents = sumOfPercents < 100
                        ? totalNonPercent * 100 / (100 - sumOfPercents) : 0;
                for (int i = 0; i < numberOfColumns; i++) {
                    if (widths[i].isPercent && widths[i].width > 0) {
                        tableWidthBasedOnPercents = Math.max(widths[i].max * 100 / widths[i].width, tableWidthBasedOnPercents);
                    }
                }

                if (tableWidthBasedOnPercents <= tableWidth) {
                    if (tableWidthBasedOnPercents >= minTableWidth) {
                        tableWidth = tableWidthBasedOnPercents;
                        //we don't need more space, columns are done based on column's max width.
                        toBalance = false;
                    } else {
                        tableWidth = minTableWidth;
                    }
                }
            }

            if (sumOfPercents > 0 && sumOfPercents < 100 && totalNonPercent == 0) {
                // each column has percent value but sum < 100%
                // upscale percents
                for (int i = 0; i < widths.length; i++) {
                    widths[i].width = 100 * widths[i].width / sumOfPercents;
                }
                sumOfPercents = 100;
            }

            if (!toBalance) {
                //column width based on max width, no need to check min width.
                for (int i = 0; i < numberOfColumns; i++) {
                    widths[i].finalWidth = widths[i].isPercent
                            ? tableWidth * widths[i].width / 100
                            : widths[i].width;
                }
            } else if (sumOfPercents >= 100) {
                sumOfPercents = 100;
                boolean recalculatePercents = false;
                float remainWidth = tableWidth - minTableWidth;
                for (int i = 0; i < numberOfColumns; i++) {
                    if (widths[i].isPercent) {
                        if (remainWidth * widths[i].width / 100 >= widths[i].min) {
                            widths[i].finalWidth = remainWidth * widths[i].width / 100;
                        } else {
                            widths[i].finalWidth = widths[i].min;
                            widths[i].isPercent = false;
                            remainWidth -= widths[i].min;
                            sumOfPercents -= widths[i].width;
                            recalculatePercents = true;
                        }
                    } else {
                        widths[i].finalWidth = widths[i].min;
                    }
                }
                if (recalculatePercents) {
                    for (int i = 0; i < numberOfColumns; i++) {
                        if (widths[i].isPercent) {
                            widths[i].finalWidth = remainWidth * widths[i].width / sumOfPercents;
                        }
                    }
                }
            } else {
                // We either have some extra space and may extend columns in case fixed table width,
                // or have to decrease columns to fit table width.
                //
                // columns shouldn't be more than its max value in case unspecified table width.
                // columns shouldn't be more than its percentage value.

                // opposite to sumOfPercents, which is sum of percent values in points.
                float totalPercent = 0;
                float minTotalNonPercent = 0;
                float fixedAddition = 0;
                float flexibleAddition = 0;
                boolean hasFlexibleCell = false;
                //sum of non fixed non percent columns.
                for (int i = 0; i < numberOfColumns; i++) {
                    if (widths[i].isPercent) {
                        if (tableWidth * widths[i].width / 100 >= widths[i].min) {
                            widths[i].finalWidth = tableWidth * widths[i].width / 100;
                            totalPercent += widths[i].finalWidth;
                        } else {
                            sumOfPercents -= widths[i].width;
                            widths[i].resetPoints(widths[i].min);
                            widths[i].finalWidth = widths[i].min;
                            minTotalNonPercent += widths[i].min;
                        }
                    } else {
                        widths[i].finalWidth = widths[i].min;
                        minTotalNonPercent += widths[i].min;
                        float addition = widths[i].width - widths[i].min;
                        if (widths[i].isFixed) {
                            fixedAddition += addition;
                        } else {
                            flexibleAddition += addition;
                            hasFlexibleCell = true;
                        }
                    }
                }
                if (totalPercent + minTotalNonPercent > tableWidth) {
                    // collision between minWidth and percent value.
                    float extraWidth = tableWidth - minTotalNonPercent;
                    if (sumOfPercents > 0) {
                        for (int i = 0; i < numberOfColumns; i++) {
                            if (widths[i].isPercent) {
                                widths[i].finalWidth = extraWidth * widths[i].width / sumOfPercents;
                            }
                        }
                    }
                } else {
                    float extraWidth = tableWidth - totalPercent - minTotalNonPercent;
                    if (fixedAddition > 0 && (extraWidth < fixedAddition || !hasFlexibleCell)) {
                        for (int i = 0; i < numberOfColumns; i++) {
                            //only points could be fixed
                            if (widths[i].isFixed) {
                                widths[i].finalWidth += (widths[i].width - widths[i].min) * extraWidth / fixedAddition;
                            }
                        }
                    } else {
                        extraWidth -= fixedAddition;
                        if (extraWidth < flexibleAddition) {
                            for (int i = 0; i < numberOfColumns; i++) {
                                if (widths[i].isFixed) {
                                    widths[i].finalWidth = widths[i].width;
                                } else if (!widths[i].isPercent) {
                                    widths[i].finalWidth += (widths[i].width - widths[i].min) * extraWidth / flexibleAddition;
                                }
                            }
                        } else {
                            float totalFixed = 0;
                            float totalFlexible = 0;
                            float flexibleCount = 0;
                            for (int i = 0; i < numberOfColumns; i++) {
                                if (widths[i].isFixed) {
                                    widths[i].finalWidth = widths[i].width;
                                    totalFixed += widths[i].width;
                                } else if (!widths[i].isPercent) {
                                    totalFlexible += widths[i].width;
                                    flexibleCount++;
                                }
                            }
                            assert totalFlexible > 0 || flexibleCount > 0;
                            extraWidth = tableWidth - totalPercent - totalFixed;
                            for (int i = 0; i < numberOfColumns; i++) {
                                if (!widths[i].isPercent && !widths[i].isFixed) {
                                    widths[i].finalWidth = totalFlexible > 0
                                            ? widths[i].width * extraWidth / totalFlexible
                                            : extraWidth / flexibleCount;
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    void processCellsRemainWidth(CellInfo cell, UnitValue cellWidth) {
        int flexibleCols = 0;
        float remainWidth = cellWidth.getValue();
        for (int i = cell.getCol(); i < cell.getCol() + cell.getColspan(); i++) {
            if (!widths[i].isPercent) {
                remainWidth -= widths[i].width;
                if (!widths[i].isFixed) {
                    flexibleCols++;
                }
            } else {
                // if any col has percent value, we cannot predict remaining width.
                remainWidth = 0;
                break;
            }
        }
        if (remainWidth > 0) {
            int[] flexibleColIndexes = ArrayUtil.fillWithValue(new int[cell.getColspan()], -1);
            if (flexibleCols > 0) {
                // check min width in columns
                for (int i = cell.getCol(); i < cell.getCol() + cell.getColspan(); i++) {
                    if (!widths[i].isFlexible())
                        continue;
                    if (widths[i].min > widths[i].width + remainWidth / flexibleCols) {
                        widths[i].resetPoints(widths[i].min);
                        remainWidth -= widths[i].min - widths[i].width;
                        flexibleCols--;
                        if (flexibleCols == 0 || remainWidth <= 0) {
                            break;
                        }
                    } else {
                        flexibleColIndexes[i - cell.getCol()] = i;
                    }
                }
                if (flexibleCols > 0 && remainWidth > 0) {
                    for (int i = 0; i < flexibleColIndexes.length; i++) {
                        if (flexibleColIndexes[i] >= 0) {
                            widths[flexibleColIndexes[i]].addPoints(remainWidth / flexibleCols).setFixed(true);
                        }
                    }
                }
            } else {
                for (int i = cell.getCol(); i < cell.getCol() + cell.getColspan(); i++) {
                    widths[i].addPoints(remainWidth / cell.getColspan());
                }
            }
        }
    }

    float[] fixedLayout() {
        float[] columnWidths = new float[numberOfColumns];
        //fill columns from col info
        for (int i = 0; i < numberOfColumns; i++) {
            UnitValue colWidth = getTable().getColumnWidth(i);
            if (colWidth == null || colWidth.getValue() < 0) {
                columnWidths[i] = -1;
            } else if (colWidth.isPercentValue()) {
                columnWidths[i] = colWidth.getValue() * tableWidth / 100;
            } else {
                columnWidths[i] = colWidth.getValue();
            }
        }
        //fill columns with -1 from cell info.
        int processedColumns = 0;
        float remainWidth = tableWidth;
        CellRenderer[] firtsRow;
        if (tableRenderer.headerRenderer != null && tableRenderer.headerRenderer.rows.size() > 0) {
            firtsRow = tableRenderer.headerRenderer.rows.get(0);
        } else if (tableRenderer.rows.size() > 0 && getTable().isComplete() && 0 == getTable().getLastRowBottomBorder().size()) {
            firtsRow = tableRenderer.rows.get(0);
        } else {
            //most likely it is large table
            firtsRow = null;
        }

        float[] columnWidthIfPercent = new float[columnWidths.length];
        for (int i = 0; i < columnWidthIfPercent.length; i++) {
            columnWidthIfPercent[i] = -1;
        }
        float sumOfPercents = 0;

        // only for not large tables
        if (firtsRow != null && getTable().isComplete() && getTable().getLastRowBottomBorder().isEmpty()) {
            for (int i = 0; i < numberOfColumns; i++) {
                if (columnWidths[i] == -1) {
                    CellRenderer cell = firtsRow[i];
                    if (cell != null) {
                        UnitValue cellWidth = getCellWidth(cell, true);
                        if (cellWidth != null) {
                            assert cellWidth.getValue() >= 0;
                            float width = 0;
                            if (cellWidth.isPercentValue()) {
                                width = tableWidth * cellWidth.getValue() / 100;
                                columnWidthIfPercent[i] = cellWidth.getValue();
                                sumOfPercents += columnWidthIfPercent[i];
                            } else {
                                width = cellWidth.getValue();
                            }
                            int colspan = ((Cell) cell.getModelElement()).getColspan();
                            for (int j = 0; j < colspan; j++) {
                                columnWidths[i + j] = width / colspan;
                            }
                            remainWidth -= columnWidths[i];
                            processedColumns++;
                        }
                    }
                } else {
                    remainWidth -= columnWidths[i];
                    processedColumns++;
                }
            }
        } else {
            for (int i = 0; i < numberOfColumns; i++) {
                if (columnWidths[i] != -1) {
                    processedColumns++;
                    remainWidth -= columnWidths[i];
                }
            }
        }
        if (sumOfPercents > 100) {
            warn100percent();
        }
        if (remainWidth > 0) {
            if (numberOfColumns == processedColumns) {
                //Set remaining width to all columns.
                for (int i = 0; i < numberOfColumns; i++) {
                    columnWidths[i] = tableWidth * columnWidths[i] / (tableWidth - remainWidth);
                }
            }
        } else if (remainWidth < 0){
            //Only columns with a width of percentage type should suffer.
            for (int i = 0; i < numberOfColumns; i++) {
                columnWidths[i] += -1 != columnWidthIfPercent[i]
                        ? remainWidth * columnWidthIfPercent[i] / sumOfPercents
                        : 0;
            }
        }
        for (int i = 0; i < numberOfColumns; i++) {
            if (columnWidths[i] == -1) {
                columnWidths[i] = Math.max(0, remainWidth / (numberOfColumns - processedColumns));
            }
        }

        // Set remaining width to the unprocessed columns.
        if (tableRenderer.bordersHandler instanceof SeparatedTableBorders) {
            for (int i = 0; i < numberOfColumns; i++) {
                columnWidths[i] += horizontalBorderSpacing;
            }
        }
        return columnWidths;
    }

    //region Common methods

    private void calculateTableWidth(float availableWidth, boolean calculateTableMaxWidth) {
        fixedTableLayout = "fixed".equals(tableRenderer
                .getProperty(Property.TABLE_LAYOUT, "auto").toLowerCase());
        UnitValue width = tableRenderer.getProperty(Property.WIDTH);
        if (fixedTableLayout && width != null && width.getValue() >= 0) {
            if (0 != getTable().getLastRowBottomBorder().size()) {
                width = getTable().getWidth();
            } else if (!getTable().isComplete() && null != getTable().getWidth() && getTable().getWidth().isPercentValue()) {
                getTable().setWidth((float) tableRenderer.retrieveUnitValue(availableWidth, Property.WIDTH));
            }
            fixedTableWidth = true;
            tableWidth = (float) retrieveTableWidth(width, availableWidth);
            layoutMinWidth = width.isPercentValue() ? 0 : tableWidth;
        } else {
            fixedTableLayout = false;
            //min width will initialize later
            layoutMinWidth = -1;
            if (calculateTableMaxWidth) {
                fixedTableWidth = false;
                tableWidth = retrieveTableWidth(availableWidth);
            } else if (width != null && width.getValue() >= 0) {
                fixedTableWidth = true;
                tableWidth = (float) retrieveTableWidth(width, availableWidth);
            } else {
                fixedTableWidth = false;
                tableWidth = retrieveTableWidth(availableWidth);
            }
        }
        Float min = retrieveTableWidth(tableRenderer.getProperty(Property.MIN_WIDTH), availableWidth);
        Float max = retrieveTableWidth(tableRenderer.getProperty(Property.MAX_WIDTH), availableWidth);

        tableMinWidth = min != null ? (float) min : layoutMinWidth;
        tableMaxWidth = max != null ? (float) max : tableWidth;

        if (tableMinWidth > tableMaxWidth)
            tableMaxWidth = tableMinWidth;

        if (tableMinWidth > tableWidth)
            tableWidth = tableMinWidth;

        if (tableMaxWidth < tableWidth)
            tableWidth = tableMaxWidth;
    }

    private Float retrieveTableWidth(UnitValue width, float availableWidth) {
        if (width == null) return null;
        return retrieveTableWidth(width.isPercentValue()
                ? width.getValue() * availableWidth / 100
                : width.getValue());
    }

    private float retrieveTableWidth(float width) {
        if (BorderCollapsePropertyValue.SEPARATE.equals(tableRenderer.getProperty(Property.BORDER_COLLAPSE))) {
            width -= (rightBorderMaxWidth + leftBorderMaxWidth);
            width -= (numberOfColumns + 1) * horizontalBorderSpacing;
        } else {
            width -= (rightBorderMaxWidth + leftBorderMaxWidth) / 2;
        }
        return Math.max(width, 0);
    }

    private Table getTable() {
        return (Table) tableRenderer.getModelElement();
    }

    //endregion

    //region Auto layout utils

    private void calculateMinMaxWidths() {
        float[] minWidths = new float[numberOfColumns];
        float[] maxWidths = new float[numberOfColumns];

        for (CellInfo cell : cells) {
            cell.setParent(tableRenderer);
            MinMaxWidth minMax = cell.getCell().getMinMaxWidth();
            if (BorderCollapsePropertyValue.SEPARATE.equals(tableRenderer.getProperty(Property.BORDER_COLLAPSE))) {
                minMax.setAdditionalWidth((float) (minMax.getAdditionalWidth() - horizontalBorderSpacing));
            } else {
                float[] indents = getCellBorderIndents(cell);
                minMax.setAdditionalWidth(minMax.getAdditionalWidth() + indents[1] / 2 + indents[3] / 2);
            }

            if (cell.getColspan() == 1) {
                minWidths[cell.getCol()] = Math.max(minMax.getMinWidth(), minWidths[cell.getCol()]);
                maxWidths[cell.getCol()] = Math.max(minMax.getMaxWidth(), maxWidths[cell.getCol()]);
            } else {
                float remainMin = minMax.getMinWidth();
                float remainMax = minMax.getMaxWidth();
                for (int i = cell.getCol(); i < cell.getCol() + cell.getColspan(); i++) {
                    remainMin -= minWidths[i];
                    remainMax -= maxWidths[i];
                }
                if (remainMin > 0) {
                    for (int i = cell.getCol(); i < cell.getCol() + cell.getColspan(); i++) {
                        minWidths[i] += remainMin / cell.getColspan();
                    }
                }
                if (remainMax > 0) {
                    for (int i = cell.getCol(); i < cell.getCol() + cell.getColspan(); i++) {
                        maxWidths[i] += remainMax / cell.getColspan();
                    }
                }
            }
        }

        for (int i = 0; i < widths.length; i++) {
            widths[i] = new ColumnWidthData(minWidths[i], maxWidths[i]);
        }
    }

    private float[] getCellBorderIndents(CellInfo cell) {
        TableRenderer renderer;
        if (cell.region == CellInfo.HEADER) {
            renderer = tableRenderer.headerRenderer;
        } else if (cell.region == CellInfo.FOOTER) {
            renderer = tableRenderer.footerRenderer;
        } else {
            renderer = tableRenderer;
        }
        return renderer.bordersHandler.getCellBorderIndents(cell.getRow(), cell.getCol(), cell.getRowspan(), cell.getColspan());
    }

    private void fillAndSortCells() {
        cells = new ArrayList<>();
        if (tableRenderer.headerRenderer != null) {
            fillRendererCells(tableRenderer.headerRenderer, CellInfo.HEADER);
        }
        fillRendererCells(tableRenderer, CellInfo.BODY);
        if (tableRenderer.footerRenderer != null) {
            fillRendererCells(tableRenderer.footerRenderer, CellInfo.FOOTER);
        }
        // Cells are sorted, because we need to process cells without colspan
        // and process from top left to bottom right for other cases.
        Collections.sort(cells);
    }

    private void fillRendererCells(TableRenderer renderer, byte region) {
        for (int row = 0; row < renderer.rows.size(); row++) {
            for (int col = 0; col < numberOfColumns; col++) {
                CellRenderer cell = renderer.rows.get(row)[col];
                if (cell != null) {
                    cells.add(new CellInfo(cell, row, col, region));
                }
            }
        }
    }

    private void warn100percent() {
        Logger logger = LoggerFactory.getLogger(TableWidths.class);
        logger.warn(IoLogMessageConstant.SUM_OF_TABLE_COLUMNS_IS_GREATER_THAN_100);
    }

    private float[] extractWidths() {
        float actualWidth = 0;
        layoutMinWidth = 0;
        float[] columnWidths = new float[widths.length];
        for (int i = 0; i < widths.length; i++) {
            assert widths[i].finalWidth >= 0;
            columnWidths[i] = widths[i].finalWidth + horizontalBorderSpacing;
            actualWidth += widths[i].finalWidth;
            layoutMinWidth += widths[i].min + horizontalBorderSpacing;
        }
        if (actualWidth > tableWidth + MinMaxWidthUtils.getEps() * widths.length) {
            Logger logger = LoggerFactory.getLogger(TableWidths.class);
            logger.warn(IoLogMessageConstant.TABLE_WIDTH_IS_MORE_THAN_EXPECTED_DUE_TO_MIN_WIDTH);
        }
        return columnWidths;
    }

    //endregion

    //region Internal classes

    @Override
    public String toString() {
        return "width=" + tableWidth + (fixedTableWidth ? "!!" : "");
    }

    private static class ColumnWidthData {
        final float min;
        float max;
        float width = 0;
        float finalWidth = -1;
        boolean isPercent = false;
        //true means that this column has cell property based width.
        boolean isFixed = false;

        ColumnWidthData(float min, float max) {
            assert min >= 0;
            assert max >= 0;
            this.min = min > 0 ? min + MinMaxWidthUtils.getEps() : 0;
            // All browsers implement a size limit on the cell's max width.
            // This limit is based on KHTML's representation that used 16 bits widths.
            this.max = max > 0 ? Math.min(max + MinMaxWidthUtils.getEps(), 32760) : 0;
        }

        ColumnWidthData setPoints(float width) {
            assert !isPercent;
            assert this.min <= width;
            this.width = Math.max(this.width, width);
            return this;
        }

        ColumnWidthData resetPoints(float width) {
            assert this.min <= width;
            this.width = width;
            this.isPercent = false;
            return this;
        }

        ColumnWidthData addPoints(float width) {
            assert !isPercent;
            this.width += width;
            return this;
        }

        ColumnWidthData setPercents(float percent) {
            if (isPercent) {
                width = Math.max(width, percent);
            } else {
                isPercent = true;
                width = percent;
            }
            isFixed = false;
            return this;
        }

        ColumnWidthData addPercents(float width) {
            assert isPercent;
            this.width += width;
            return this;
        }

        ColumnWidthData setFixed(boolean fixed) {
            this.isFixed = fixed;
            return this;
        }

        boolean isFlexible() {
            return !this.isFixed && !this.isPercent;
        }

        @Override
        public String toString() {
            return "w=" + width +
                    (isPercent ? "%" : "pt") +
                    (isFixed ? " !!" : "") +
                    ", min=" + min +
                    ", max=" + max +
                    ", finalWidth=" + finalWidth;
        }
    }

    static private final UnitValue ZeroWidth = UnitValue.createPointValue(0);

    /**
     * Gets width of the cell, adding paddings and extra spacing if necessary.
     *
     * @param cell renderer from which width will be taken.
     *             Note that this method will not change original width of the element.
     * @param zeroIsValid defines if 0 width is valid
     * @return increased width of the renderer
     */
    private UnitValue getCellWidth(CellRenderer cell, boolean zeroIsValid) {
        UnitValue widthValue = new UnitValue(cell.getProperty(Property.WIDTH, UnitValue.createPointValue(-1)));
        //zero has special meaning in fixed layout, we shall not add padding to zero value
        if (widthValue.getValue() < -AbstractRenderer.EPS) {
            return null;
        }

        if (widthValue.getValue() < AbstractRenderer.EPS) {
            return zeroIsValid ? ZeroWidth : null;
        } else if (widthValue.isPercentValue()) {
            return widthValue;
        } else {
            widthValue = resolveMinMaxCollision(cell, widthValue);
            if (!AbstractRenderer.isBorderBoxSizing(cell)) {
                Border[] borders = cell.getBorders();
                if (borders[1] != null) {
                    widthValue.setValue(widthValue.getValue() +
                            ((tableRenderer.bordersHandler instanceof SeparatedTableBorders)
                                    ? borders[1].getWidth()
                                    : borders[1].getWidth() / 2));
                }
                if (borders[3] != null) {
                    widthValue.setValue(widthValue.getValue() +
                            ((tableRenderer.bordersHandler instanceof SeparatedTableBorders)
                                    ? borders[3].getWidth()
                                    : borders[3].getWidth() / 2));
                }
                UnitValue[] paddings = cell.getPaddings();
                if (!paddings[1].isPointValue()) {
                    Logger logger = LoggerFactory.getLogger(TableWidths.class);
                    logger.error(MessageFormatUtil.format(IoLogMessageConstant.PROPERTY_IN_PERCENTS_NOT_SUPPORTED,
                            Property.PADDING_LEFT));
                }
                if (!paddings[3].isPointValue()) {
                    Logger logger = LoggerFactory.getLogger(TableWidths.class);
                    logger.error(MessageFormatUtil.format(IoLogMessageConstant.PROPERTY_IN_PERCENTS_NOT_SUPPORTED,
                            Property.PADDING_RIGHT));
                }
                widthValue.setValue(widthValue.getValue() + paddings[1].getValue() + paddings[3].getValue());
            }
            return widthValue;
        }
    }

    private UnitValue resolveMinMaxCollision(CellRenderer cell, UnitValue widthValue) {
        assert widthValue.isPointValue();

        UnitValue minWidthValue = cell.getProperty(Property.MIN_WIDTH);
        if (minWidthValue != null && minWidthValue.isPointValue()
                && minWidthValue.getValue() > widthValue.getValue()) {
            return minWidthValue;
        }
        UnitValue maxWidthValue = cell.getProperty(Property.MAX_WIDTH);
        if (maxWidthValue != null && maxWidthValue.isPointValue()
                && maxWidthValue.getValue() < widthValue.getValue()) {
            return maxWidthValue;
        }
        return widthValue;
    }

    static class CellInfo implements Comparable {
        static final byte HEADER = 1;
        static final byte BODY = 2;
        static final byte FOOTER = 3;

        private final CellRenderer cell;
        private final int row;
        private final int col;
        final byte region;

        CellInfo(CellRenderer cell, int row, int col, byte region) {
            this.cell = cell;
            this.region = region;
            //we cannot use getModelElement().getCol() or getRow(), because its may be changed during layout.
            this.row = row;
            this.col = col;
        }

        CellRenderer getCell() {
            return cell;
        }

        int getCol() {
            return col;
        }

        int getColspan() {
            //we cannot use getModelElement().getColspan(), because it may be changed during layout.
            return (int) cell.getPropertyAsInteger(Property.COLSPAN);
        }

        int getRow() {
            return row;
        }

        int getRowspan() {
            //we cannot use getModelElement().getRowspan(), because it may be changed during layout.
            return (int) cell.getPropertyAsInteger(Property.ROWSPAN);
        }

        @Override
        public int compareTo(CellInfo o) {
            if (getColspan() == 1 ^ o.getColspan() == 1) {
                return getColspan() - o.getColspan();
            }
            if (region == o.region && getRow() == o.getRow()) {
                return getCol() + getColspan() - o.getCol() - o.getColspan();
            }
            return region == o.region ? getRow() - o.getRow() : region - o.region;
        }

        @Override
        public String toString() {
            String str = MessageFormatUtil.format("row={0}, col={1}, rowspan={2}, colspan={3}, ",
                    getRow(), getCol(), getRowspan(), getColspan());
            if (region == HEADER) {
                str += "header";
            } else if (region == BODY) {
                str += "body";
            } else if (region == FOOTER) {
                str += "footer";
            }
            return str;
        }

        public void setParent(TableRenderer tableRenderer) {
            if (region == HEADER) {
                cell.setParent(tableRenderer.headerRenderer);
            } else if (region == FOOTER) {
                cell.setParent(tableRenderer.footerRenderer);
            } else {
                cell.setParent(tableRenderer);
            }
        }
    }

    //endregion
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy