YetiForceCompany/YetiForcePDF

View on GitHub
lib/Layout/TableBox.php

Summary

Maintainability
F
1 wk
Test Coverage
<?php

declare(strict_types=1);
/**
 * TableBox class.
 *
 * @package   YetiForcePDF\Layout
 *
 * @copyright YetiForce Sp. z o.o
 * @license   MIT
 * @author    Rafal Pospiech <r.pospiech@yetiforce.com>
 */

namespace YetiForcePDF\Layout;

use YetiForcePDF\Html\Element;
use YetiForcePDF\Math;
use YetiForcePDF\Style\Style;

/**
 * Class TableBox.
 */
class TableBox extends BlockBox
{
    /**
     * @var array minimal widths
     */
    protected $minWidths = [];
    /**
     * @var array preferred widths
     */
    protected $preferredWidths = [];
    /**
     * @var array maximal widths
     */
    protected $contentWidths = [];
    /**
     * @var string total min width
     */
    protected $minWidth = '0';
    /**
     * @var string total preferred width
     */
    protected $preferredWidth = '0';
    /**
     * @var string total max width
     */
    protected $contentWidth = '0';
    /**
     * @var array percentages for each percentage column
     */
    protected $percentages = [];
    /**
     * @var string cell spacing total width
     */
    protected $cellSpacingWidth = '0';
    /**
     * @var string total border width
     */
    protected $borderWidth = '0';
    /**
     * @var array percentage columns
     */
    protected $percentColumns = [];
    /**
     * @var array pixel columns
     */
    protected $pixelColumns = [];
    /**
     * @var array auto width columns
     */
    protected $autoColumns = [];
    /**
     * @var array saving state
     */
    protected $beforeWidths = [];
    /**
     * @var array rows
     */
    protected $rows = [];
    /**
     * @var TableRowGroupBox|null
     */
    protected $anonymousRowGroup;
    /**
     * Parent width cache.
     *
     * @var string
     */
    protected $parentWidth = '0';

    /**
     * We shouldn't append block box here.
     *
     * @param mixed $childDomElement
     * @param mixed $element
     * @param mixed $style
     * @param mixed $parentBlock
     */
    public function appendBlockBox($childDomElement, $element, $style, $parentBlock)
    {
    }

    /**
     * We shouldn't append table wrapper here.
     *
     * @param mixed $childDomElement
     * @param mixed $element
     * @param mixed $style
     * @param mixed $parentBlock
     */
    public function appendTableWrapperBox($childDomElement, $element, $style, $parentBlock)
    {
    }

    /**
     * We shouldn't append inline block box here.
     *
     * @param mixed $childDomElement
     * @param mixed $element
     * @param mixed $style
     * @param mixed $parentBlock
     */
    public function appendInlineBlockBox($childDomElement, $element, $style, $parentBlock)
    {
    }

    /**
     * We shouldn't append inline box here.
     *
     * @param mixed $childDomElement
     * @param mixed $element
     * @param mixed $style
     * @param mixed $parentBlock
     */
    public function appendInlineBox($childDomElement, $element, $style, $parentBlock)
    {
    }

    /**
     * Create row group inside table
     * return TableRowGroupBox.
     */
    public function createRowGroupBox()
    {
        $style = (new \YetiForcePDF\Style\Style())
            ->setDocument($this->document)
            ->setContent('')
            ->parseInline();
        $box = (new TableRowGroupBox())
            ->setDocument($this->document)
            ->setParent($this)
            ->setStyle($style)
            ->init();
        $this->appendChild($box);
        $box->getStyle()->init();

        return $box;
    }

    /**
     * Append table row group box element.
     *
     * @param \DOMNode                      $childDomElement
     * @param Element                       $element
     * @param Style                         $style
     * @param \YetiForcePDF\Layout\BlockBox $parentBlock
     * @param string                        $display
     *
     * @return $this
     */
    public function appendTableRowGroupBox($childDomElement, $element, $style, $parentBlock, string $display)
    {
        $cleanStyle = (new \YetiForcePDF\Style\Style())
            ->setDocument($this->document)
            ->setContent('')
            ->parseInline();
        $rowGroupClass = 'YetiForcePDF\\Layout\\TableRowGroupBox';
        switch ($display) {
            case 'table-header-group':
                $rowGroupClass = 'YetiForcePDF\\Layout\\TableHeaderGroupBox';

                break;
            case 'table-footer-group':
                $rowGroupClass = 'YetiForcePDF\\Layout\\TableFooterGroupBox';

                break;
        }
        $box = (new $rowGroupClass())
            ->setDocument($this->document)
            ->setParent($this)
            ->setElement($element)
            ->setStyle($cleanStyle)
            ->init();
        $this->appendChild($box);
        $box->getStyle()->init()->setRule('display', 'block');
        $box->buildTree($box);

        return $box;
    }

    /**
     * Append table row group box element.
     *
     * @param \DOMNode                      $childDomElement
     * @param Element                       $element
     * @param Style                         $style
     * @param \YetiForcePDF\Layout\BlockBox $parentBlock
     *
     * @return $this
     */
    public function appendTableRowBox($childDomElement, $element, $style, $parentBlock)
    {
        $box = (new TableRowBox())
            ->setDocument($this->document)
            ->setParent($this)
            ->setElement($element)
            ->setStyle($style)
            ->init();
        $this->appendChild($box);
        $box->getStyle()->init()->setRule('display', 'block');
        $box->buildTree($box);

        return $box;
    }

    /**
     * Get all rows from all row groups.
     *
     * @return array of LineBoxes and TableRowBoxes
     */
    public function getRows()
    {
        $rows = [];
        foreach ($this->getChildren() as $rowGroup) {
            if ($rowGroup instanceof TableRowGroupBox) {
                foreach ($rowGroup->getChildren() as $row) {
                    $rows[] = $row;
                }
            }
        }

        return $rows;
    }

    /**
     * Get columns - get table cells segregated by columns.
     *
     * @return array
     */
    public function getColumns()
    {
        $columns = [];
        foreach ($this->getChildren() as $rowGroup) {
            foreach ($rowGroup->getChildren() as $row) {
                foreach ($row->getChildren() as $columnIndex => $column) {
                    if ($column instanceof TableColumnBox) {
                        $columns[$columnIndex][] = $column;
                    }
                }
            }
        }

        return $columns;
    }

    /**
     * Get cells.
     *
     * @return array
     */
    public function getCells()
    {
        $cells = [];
        foreach ($this->getChildren() as $rowGroup) {
            foreach ($rowGroup->getChildren() as $row) {
                foreach ($row->getChildren() as $column) {
                    $cells[] = $column->getFirstChild();
                }
            }
        }

        return $cells;
    }

    /**
     * Get minimal and maximal column widths.
     *
     * @param string $availableSpace
     *
     * @return array
     */
    public function setUpWidths(string $availableSpace)
    {
        foreach ($this->getChildren() as $rowGroup) {
            foreach ($rowGroup->getChildren() as $row) {
                if ($columns = $row->getChildren()) {
                    foreach ($columns as $columnIndex => $column) {
                        $cell = $column->getFirstChild();
                        $cellStyle = $cell->getStyle();
                        $columnInnerWidth = $cell->getDimensions()->getMaxWidth();
                        $styleWidth = $column->getStyle()->getRules('width');
                        $this->contentWidths[$columnIndex] = Math::max($this->contentWidths[$columnIndex] ?? '0', $columnInnerWidth);
                        $minColumnWidth = $cell->getDimensions()->getMinWidth();
                        if ($column->getColSpan() > 1) {
                            $minColumnWidth = Math::div($minColumnWidth, (string) $column->getColSpan());
                        }
                        $this->minWidths[$columnIndex] = Math::max($this->minWidths[$columnIndex] ?? '0', $minColumnWidth);
                        if ('auto' !== $styleWidth && false === strpos($styleWidth, '%')) {
                            if ($column->getColSpan() > 1) {
                                $styleWidth = Math::div($styleWidth, (string) $column->getColSpan());
                            }
                            $preferred = Math::max($styleWidth, $minColumnWidth);
                            $this->minWidths[$columnIndex] = $preferred;
                        } elseif (strpos($styleWidth, '%') > 0) {
                            $preferred = Math::max($this->preferredWidths[$columnIndex] ?? '0', $columnInnerWidth);
                            $this->percentages[$columnIndex] = Math::max($this->percentages[$columnIndex] ?? '0', trim($styleWidth, '%'));
                        } else {
                            $preferred = Math::max($this->preferredWidths[$columnIndex] ?? '0', $columnInnerWidth);
                        }
                        $this->preferredWidths[$columnIndex] = $preferred;
                    }
                    $this->borderWidth = Math::add($this->borderWidth, $cellStyle->getHorizontalBordersWidth());
                    $this->minWidth = Math::add($this->minWidth, $this->minWidths[$columnIndex]);
                    $this->contentWidth = Math::add($this->contentWidth, $this->contentWidths[$columnIndex]);
                    $this->preferredWidth = Math::add($this->preferredWidth, $this->preferredWidths[$columnIndex]);
                }
            }
        }
        if ('collapse' !== $this->getParent()->getStyle()->getRules('border-collapse')) {
            $spacing = $this->getStyle()->getRules('border-spacing');
            $this->cellSpacingWidth = Math::mul((string) (\count($columns) + 1), $spacing);
        }
    }

    /**
     * Set up sizing types for columns.
     *
     * @return $this
     */
    protected function setUpSizingTypes()
    {
        $columnSizingTypes = [];
        // rowGroup -> row -> columns
        $columns = $this->getFirstChild()->getFirstChild()->getChildren();
        foreach ($columns as $columnIndex => $column) {
            $columnStyleWidth = $column->getStyle()->getRules('width');
            if (strpos($columnStyleWidth, '%') > 0) {
                $columnSizingTypes[$columnIndex] = 'percent';
            } elseif ('auto' !== $columnStyleWidth) {
                $columnSizingTypes[$columnIndex] = 'pixel';
            } else {
                $columnSizingTypes[$columnIndex] = 'auto';
            }
        }
        $this->percentColumns = [];
        $this->pixelColumns = [];
        $this->autoColumns = [];
        foreach ($this->getChildren() as $rowGroup) {
            foreach ($rowGroup->getChildren() as $row) {
                foreach ($row->getChildren() as $columnIndex => $column) {
                    if (isset($columnSizingTypes[$columnIndex]) && 'percent' === $columnSizingTypes[$columnIndex]) {
                        $this->percentColumns[$columnIndex][] = $column;
                    } elseif (isset($columnSizingTypes[$columnIndex]) && 'pixel' === $columnSizingTypes[$columnIndex]) {
                        $this->pixelColumns[$columnIndex][] = $column;
                    } else {
                        $this->autoColumns[$columnIndex][] = $column;
                    }
                }
            }
        }
        unset($columnSizingTypes);

        return $this;
    }

    /**
     * Set rows width.
     *
     * @param string $width
     *
     * @return $this
     */
    protected function setRowsWidth()
    {
        $width = '0';
        if (empty($this->rows)) {
            return $this;
        }
        foreach ($this->rows[0]->getChildren() as $column) {
            $width = Math::add($width, $column->getDimensions()->getWidth());
        }
        foreach ($this->rows as $row) {
            $rowStyle = $row->getStyle();
            $rowWidth = $width;
            if ('separate' === $this->getStyle()->getRules('border-collapse')) {
                $rowSpacing = Math::add($rowStyle->getHorizontalPaddingsWidth(), $rowStyle->getHorizontalBordersWidth());
                $rowWidth = Math::add($rowWidth, $rowSpacing);
            }
            $row->getDimensions()->setWidth($rowWidth);
        }
        $row->getParent()->getDimensions()->setWidth($rowWidth);
        $style = $this->getStyle();
        $spacing = Math::add($style->getHorizontalPaddingsWidth(), $style->getHorizontalBordersWidth());
        $rowWidth = Math::add($rowWidth, $spacing);
        $this->getDimensions()->setWidth($rowWidth);

        return $this;
    }

    /**
     * Minimal content guess.
     *
     * @param array $rows
     *
     * @return $this
     */
    protected function minContentGuess(array $rows)
    {
        foreach ($rows as $row) {
            foreach ($row->getChildren() as $columnIndex => $column) {
                $this->setColumnWidth($column, $this->minWidths[$columnIndex]);
            }
        }
        $this->setRowsWidth();

        return $this;
    }

    /**
     * Add to preferred others (add left space to preferred width of auto/pixel columns).
     *
     * @param string $leftSpace
     *
     * @return string
     */
    protected function addToPreferredOthers(string $leftSpace)
    {
        $autoNeededTotal = '0';
        $pixelNeededTotal = '0';
        $autoNeeded = [];
        $pixelNeeded = [];
        foreach ($this->autoColumns as $columnIndex => $columns) {
            $colDmns = $columns[0]->getDimensions();
            $colWidth = $colDmns->getInnerWidth();
            if (Math::comp($this->preferredWidths[$columnIndex], $colWidth) > 0) {
                $autoNeeded[$columnIndex] = Math::sub($this->preferredWidths[$columnIndex], $colWidth);
                $autoNeededTotal = Math::add($autoNeededTotal, $autoNeeded[$columnIndex]);
            }
        }
        foreach ($this->pixelColumns as $columnIndex => $columns) {
            $colDmns = $columns[0]->getDimensions();
            $colWidth = $colDmns->getInnerWidth();
            if (Math::comp($this->preferredWidths[$columnIndex], $colWidth) > 0) {
                $pixelNeeded[$columnIndex] = Math::sub($this->preferredWidths[$columnIndex], $colWidth);
                $pixelNeededTotal = Math::add($pixelNeededTotal, $pixelNeeded[$columnIndex]);
            }
        }
        // ok, we know where we need to add extra space
        $totalNeeded = Math::add($autoNeededTotal, $pixelNeededTotal);
        $totalToAdd = Math::min($leftSpace, $totalNeeded);
        // we know how much we can distribute
        Math::setAccurate(true);
        $autoTotalRatio = Math::div($autoNeededTotal, $totalNeeded);
        $addToAutoTotal = Math::mul($autoTotalRatio, $totalToAdd);
        Math::setAccurate(false);
        $addToPixelTotal = Math::sub($totalToAdd, $addToAutoTotal);
        // we know how much space we can add to each column type (auto and pixel)
        // now we must distribute this space according to concrete column needs
        foreach ($this->autoColumns as $columnIndex => $columns) {
            if (isset($autoNeeded[$columnIndex])) {
                Math::setAccurate(true);
                $neededRatio = Math::div($autoNeeded[$columnIndex], $autoNeededTotal);
                $add = Math::mul($neededRatio, $addToAutoTotal);
                Math::setAccurate(false);
                $columnWidth = Math::add($columns[0]->getDimensions()->getWidth(), $add);
                foreach ($columns as $column) {
                    $colDmns = $column->getDimensions();
                    $colDmns->setWidth($columnWidth);
                    $column->getFirstChild()->getDimensions()->setWidth($colDmns->getInnerWidth());
                }
            }
        }
        foreach ($this->pixelColumns as $columnIndex => $columns) {
            if (isset($pixelNeeded[$columnIndex])) {
                Math::setAccurate(true);
                $neededRatio = Math::div($pixelNeeded[$columnIndex], $pixelNeededTotal);
                $add = Math::mul($neededRatio, $addToPixelTotal);
                Math::setAccurate(false);
                $columnWidth = Math::add($columns[0]->getDimensions()->getWidth(), $add);
                foreach ($columns as $column) {
                    $colDmns = $column->getDimensions();
                    $colDmns->setWidth($columnWidth);
                    $column->getFirstChild()->getDimensions()->setWidth($colDmns->getInnerWidth());
                }
            }
        }

        return Math::sub($leftSpace, $totalToAdd);
    }

    /**
     * Get current others width (auto, pixel columns).
     *
     * @return string
     */
    protected function getCurrentOthersWidth()
    {
        $currentOthersWidth = '0';
        foreach ($this->autoColumns as $columns) {
            $currentOthersWidth = Math::add($currentOthersWidth, $columns[0]->getDimensions()->getInnerWidth());
        }
        foreach ($this->pixelColumns as $columns) {
            $currentOthersWidth = Math::add($currentOthersWidth, $columns[0]->getDimensions()->getInnerWidth());
        }

        return $currentOthersWidth;
    }

    /**
     * Get total percentage.
     *
     * @return string
     */
    protected function getTotalPercentage()
    {
        $totalPercentageSpecified = '0';
        foreach ($this->percentColumns as $columnIndex => $columns) {
            $totalPercentageSpecified = Math::add($totalPercentageSpecified, $this->percentages[$columnIndex]);
        }

        return $totalPercentageSpecified;
    }

    /**
     * Get total percentages width.
     *
     * @return string
     */
    protected function getTotalPercentageWidth()
    {
        $totalPercentageColumnsWidth = '0';
        foreach ($this->percentColumns as $columns) {
            $totalPercentageColumnsWidth = Math::add($totalPercentageColumnsWidth, $columns[0]->getDimensions()->getInnerWidth());
        }

        return $totalPercentageColumnsWidth;
    }

    /**
     * Expand percents to min width.
     *
     * @param string $availableSpace
     *
     * @return $this
     */
    protected function expandPercentsToMin(string $availableSpace)
    {
        $totalPercentageSpecified = $this->getTotalPercentage();
        $maxPercentRatio = '0';
        $maxPercentRatioIndex = 0;
        $ratioPercent = '0';
        foreach ($this->percentages as $columnIndex => $percent) {
            Math::setAccurate(true);
            $ratio = Math::div($this->minWidths[$columnIndex], $percent);
            Math::setAccurate(false);
            if (Math::comp($ratio, $maxPercentRatio) > 0) {
                $maxPercentRatio = $ratio;
                $maxPercentRatioIndex = $columnIndex;
                $ratioPercent = $percent;
            }
        }
        $minWidth = $this->minWidths[$maxPercentRatioIndex];
        // lowerPercent = minWidth
        $onePercent = Math::div($minWidth, $ratioPercent);
        // we have one percent width, we must apply this to all percentages and other columns
        $currentPercentsWidth = '0';
        foreach ($this->percentColumns as $columnIndex => $columns) {
            $columnWidth = Math::mul($this->percentages[$columnIndex], $onePercent);
            foreach ($columns as $column) {
                $columnStyle = $column->getStyle();
                $column->getDimensions()->setWidth(Math::add($columnWidth, $columnStyle->getHorizontalPaddingsWidth()));
                $column->getFirstChild()->getDimensions()->setWidth($columnWidth);
            }
            $this->minWidths[$columnIndex] = $columnWidth;
            $currentPercentsWidth = Math::add($currentPercentsWidth, $column->getDimensions()->getInnerWidth());
        }
        // percentage columns are satisfied, other columns must fulfill percentages
        $otherPercent = Math::sub('100', $totalPercentageSpecified);
        $othersShouldHaveWidth = Math::mul($otherPercent, $onePercent);
        if (Math::comp(Math::add($othersShouldHaveWidth, $currentPercentsWidth), $availableSpace) > 0) {
            $othersShouldHaveWidth = Math::sub($availableSpace, $currentPercentsWidth);
        }
        $currentOthersWidth = $this->getCurrentOthersWidth();
        $leftSpace = Math::sub($othersShouldHaveWidth, $currentOthersWidth);
        $this->addToOthers($leftSpace);

        return $this;
    }

    /**
     * Apply percentage dimensions.
     *
     * @param string $availableSpace
     *
     * @return $this
     */
    protected function applyPercentage(string $availableSpace)
    {
        $currentRowsWidth = '0';
        if ('auto' === $this->getParent()->getStyle()->getRules('width')) {
            foreach ($this->getRows()[0]->getChildren() as $columnIndex => $column) {
                $currentRowsWidth = Math::add($currentRowsWidth, $column->getDimensions()->getInnerWidth());
            }
        } else {
            $currentRowsWidth = $this->getParent()->getDimensions()->getInnerWidth();
            if ('separate' === $this->getStyle()->getRules('border-collapse')) {
                $rowStyle = $this->getRows()[0]->getStyle();
                $spacing = Math::add($rowStyle->getHorizontalPaddingsWidth(), $rowStyle->getHorizontalBordersWidth());
                $currentRowsWidth = Math::sub($currentRowsWidth, $spacing);
            }
        }
        $mustExpand = false;
        foreach ($this->percentColumns as $columnIndex => $columns) {
            $columnWidth = Math::percent($this->percentages[$columnIndex], $currentRowsWidth);
            if (Math::comp($this->minWidths[$columnIndex], $columnWidth) > 0) {
                // we need to expand proportionally
                $mustExpand = true;

                break;
            }
        }
        if ($mustExpand) {
            $this->expandPercentsToMin($availableSpace);
        } else {
            // everything is ok we can resize percentages
            $percentsWidth = '0';
            foreach ($this->percentColumns as $columnIndex => $columns) {
                $columnWidth = Math::percent($this->percentages[$columnIndex], $currentRowsWidth);
                $percentsWidth = Math::add($percentsWidth, $columnWidth);
                $padding = $columns[0]->getStyle()->getHorizontalPaddingsWidth();
                $columnWidth = Math::sub($columnWidth, $padding);
                foreach ($columns as $column) {
                    $this->setColumnWidth($column, $columnWidth);
                }
            }
            $totalPercentage = $this->getTotalPercentage();
            if (0 !== Math::comp($totalPercentage, '100') && 0 === Math::comp($this->getCurrentOthersWidth(), '0')) {
                // we have some space available
                $leftSpace = Math::sub($availableSpace, $percentsWidth);
                $add = Math::div($leftSpace, (string) \count($this->percentColumns));
                foreach ($this->percentColumns as $columnIndex => $columns) {
                    foreach ($columns as $column) {
                        $columnWidth = Math::add($column->getDimensions()->getWidth(), $add);
                        $column->getDimensions()->setWidth($columnWidth);
                        $column->getFirstChild()->getDimensions()->setWidth($column->getDimensions()->getInnerWidth());
                    }
                }
            }
        }

        return $this;
    }

    /**
     * Save current columns width state.
     *
     * @param array $rows
     *
     * @return $this
     */
    protected function saveState(array $rows)
    {
        $this->beforeWidths = [];
        foreach ($rows[0]->getChildren() as $columnIndex => $column) {
            $this->beforeWidths[$columnIndex] = $column->getDimensions()->getWidth();
        }

        return $this;
    }

    /**
     * Minimal content percentage guess.
     *
     * @param array  $rows
     * @param string $availableSpace
     *
     * @return $this
     */
    protected function minContentPercentageGuess(array $rows, string $availableSpace)
    {
        $this->saveState($rows);
        $this->applyPercentage($availableSpace);
        $this->setRowsWidth();

        return $this;
    }

    /**
     * Minimal content specified guess.
     *
     * @param array  $rows
     * @param string $availableSpace
     *
     * @return $this
     */
    protected function minContentSpecifiedGuess(array $rows, string $availableSpace)
    {
        $this->saveState($rows);
        $leftWidth = '0';
        foreach ($this->pixelColumns as $columnIndex => $columns) {
            foreach ($columns as $column) {
                $this->setColumnWidth($column, $this->preferredWidths[$columnIndex]);
            }
            $leftWidth = Math::add($leftWidth, $column->getDimensions()->getWidth());
        }
        foreach ($this->autoColumns as $columnIndex => $columns) {
            $leftWidth = Math::add($leftWidth, $columns[0]->getDimensions()->getWidth());
        }

        $this->applyPercentage($availableSpace);
        $this->setRowsWidth();

        return $this;
    }

    /**
     * Set column width.
     *
     * @param $column
     * @param string $width
     */
    protected function setColumnWidth($column, string $width)
    {
        $columnStyle = $column->getStyle();
        $cell = $column->getFirstChild();
        $width = Math::add($width, $columnStyle->getHorizontalPaddingsWidth());
        $column->getDimensions()->setWidth($width);
        $cell->getDimensions()->setWidth($column->getDimensions()->getInnerWidth());
    }

    /**
     * Maximal content guess.
     *
     * @param array  $rows
     * @param string $availableSpace
     *
     * @return $this
     */
    protected function maxContentGuess(array $rows, string $availableSpace)
    {
        $this->saveState($rows);
        foreach ($this->autoColumns as $columnIndex => $columns) {
            foreach ($columns as $column) {
                $this->setColumnWidth($column, $this->contentWidths[$columnIndex]);
            }
        }
        $this->applyPercentage($availableSpace);
        $this->setRowsWidth();

        return $this;
    }

    /**
     * Span rows.
     *
     * @return $this
     */
    public function spanRows()
    {
        $toRemove = [];
        foreach ($this->getChildren() as $rowGroup) {
            foreach ($rowGroup->getChildren() as $rowIndex => $row) {
                foreach ($row->getChildren() as $columnIndex => $column) {
                    if ($column->getRowSpan() > 1) {
                        $rowSpans = $column->getRowSpan();
                        $spanHeight = '0';
                        for ($i = 1; $i < $rowSpans; ++$i) {
                            $spanColumn = $row->getParent()->getChildren()[$rowIndex + $i]->getChildren()[$columnIndex];
                            $spanHeight = Math::add($spanHeight, $spanColumn->getDimensions()->getHeight());
                            $toRemove[] = $spanColumn;
                        }
                        $colDmns = $column->getDimensions();
                        $colDmns->setHeight(Math::add($colDmns->getHeight(), $spanHeight));
                        $cell = $column->getFirstChild();
                        $colInnerHeight = $colDmns->getInnerHeight();
                        $cell->getDimensions()->setHeight($colInnerHeight);
                        $cellHeight = '0';
                        foreach ($cell->getChildren() as $cellChild) {
                            $cellHeight = Math::add($cellHeight, $cellChild->getDimensions()->getOuterHeight());
                        }
                        $columnStyle = $column->getStyle();
                        $cellStyle = $column->getFirstChild()->getStyle();
                        if ('collapse' === $columnStyle->getRules('border-collapse') && $rowIndex + $i === \count($this->getChildren())) {
                            // TODO: store original border widths inside cell
                            $cellStyle->setRule('border-bottom-width', $cellStyle->getRules('border-top-width'));
                        }
                        $toDisposition = Math::sub($colInnerHeight, $cellHeight);
                        switch ($cellStyle->getRules('vertical-align')) {
                            case 'baseline':
                            case 'middle':
                                $padding = Math::div($toDisposition, '2');
                                $cellStyle->setRule('padding-top', $padding);
                                $cellStyle->setRule('padding-bottom', $padding);
                                break;
                            case 'top':
                                $cellStyle->setRule('padding-bottom', $toDisposition);
                                break;
                            case 'bottom':
                                $cellStyle->setRule('padding-top', $toDisposition);
                                break;
                        }
                        $cell->measureWidth();
                        $cell->measureHeight();
                        $cell->measureOffset();
                        $cell->alignText();
                        $cell->measurePosition();
                    }
                }
            }
        }
        foreach ($toRemove as $remove) {
            $remove->setDisplayable(false)->setRenderable(false)->setForMeasurement(false);
        }

        return $this;
    }

    /**
     * Finish table width calculations.
     *
     * @return $this
     */
    protected function finish()
    {
        foreach ($this->rows as $row) {
            $row->spanColumns();
        }
        $style = $this->getStyle();
        $width = $this->rows[0]->getDimensions()->getWidth();
        $width = Math::add($width, $style->getHorizontalPaddingsWidth(), $style->getHorizontalBordersWidth());
        $this->getDimensions()->setWidth($width);
        $parent = $this->getParent();
        $parentStyle = $parent->getStyle();
        if ('auto' === $parentStyle->getRules('width')) {
            $parentSpacing = Math::add($parentStyle->getHorizontalBordersWidth(), $parentStyle->getHorizontalPaddingsWidth());
            $width = Math::add($width, $parentSpacing);
            $parent->getDimensions()->setWidth($width);
        } else {
            $parent->applyStyleWidth();
        }
        foreach ($this->getCells() as $cell) {
            $cell->measureWidth();
        }

        return $this;
    }

    /**
     * Check whenever table fill fit to available space.
     *
     * @param string $availableSpace
     *
     * @return bool
     */
    protected function willFit(string $availableSpace)
    {
        $row = $this->rows[0];
        $width = $row->getDimensions()->getWidth();
        $width = Math::add($width, $this->getStyle()->getHorizontalBordersWidth(), $this->getStyle()->getHorizontalPaddingsWidth());

        return Math::comp($availableSpace, $width) >= 0;
    }

    /**
     * Get row inner width.
     *
     * @return string
     */
    protected function getRowInnerWidth()
    {
        $width = '0';
        foreach ($this->rows[0]->getChildren() as $column) {
            $width = Math::add($width, $column->getDimensions()->getWidth());
        }

        return $width;
    }

    /**
     * Get auto columns max width.
     *
     * @return string
     */
    protected function getAutoColumnsMaxWidth()
    {
        $autoColumnsMaxWidth = '0';
        foreach ($this->autoColumns as $columnIndex => $columns) {
            $autoColumnsMaxWidth = Math::add($autoColumnsMaxWidth, $this->contentWidths[$columnIndex]);
        }

        return $autoColumnsMaxWidth;
    }

    /**
     * Get auto columns min width.
     *
     * @return string
     */
    protected function getAutoColumnsMinWidth()
    {
        $autoColumnsMinWidth = '0';
        foreach ($this->autoColumns as $columnIndex => $columns) {
            $autoColumnsMinWidth = Math::add($autoColumnsMinWidth, $this->minWidths[$columnIndex]);
        }

        return $autoColumnsMinWidth;
    }

    /**
     * Get auto columns width.
     *
     * @return string
     */
    protected function getAutoColumnsWidth()
    {
        $autoColumnsWidth = '0';
        foreach ($this->autoColumns as $columns) {
            $autoColumnsWidth = Math::add($autoColumnsWidth, $columns[0]->getDimensions()->getInnerWidth());
        }

        return $autoColumnsWidth;
    }

    /**
     * Shrink to fit.
     *
     * @param string $availableSpace
     * @param int    $step
     *
     * @return TableBox
     */
    protected function shrinkToFit(string $availableSpace, int $step)
    {
        $parentStyle = $this->getParent()->getStyle();
        $parentSpacing = Math::add($parentStyle->getHorizontalBordersWidth(), $parentStyle->getHorizontalPaddingsWidth());
        $availableSpace = Math::sub($availableSpace, $this->cellSpacingWidth, $parentSpacing);
        $currentWidth = Math::sub($this->getRowInnerWidth(), $this->cellSpacingWidth);
        $toRemoveTotal = Math::sub($currentWidth, $availableSpace);
        $totalPercentages = '0';
        foreach ($this->percentages as $percentage) {
            $totalPercentages = Math::add($totalPercentages, $percentage);
        }
        $percentagesFullWidth = Math::percent($totalPercentages, $availableSpace);
        $eachPercentagesWidth = [];
        foreach ($this->percentages as $columnIndex => $percent) {
            $eachPercentagesWidth[$columnIndex] = Math::percent($percent, $availableSpace);
        }
        $nonPercentageSpace = Math::sub($availableSpace, $percentagesFullWidth);
        $autoColumnsMinWidth = $this->getAutoColumnsMinWidth();
        $autoColumnsMaxWidth = $this->getAutoColumnsMaxWidth();
        $totalPixelWidth = '0';
        foreach ($this->pixelColumns as $columnIndex => $columns) {
            $totalPixelWidth = Math::add($totalPixelWidth, $this->preferredWidths[$columnIndex]);
        }
        switch ($step) {
            case 0:
                // minimal stays minimal - decreasing percents
                $rowWidth = '0';
                foreach ($this->percentColumns as $columnIndex => $columns) {
                    $totalPercent = Math::div($this->percentages[$columnIndex], $totalPercentages);
                    $toRemove = Math::percent($totalPercent, $toRemoveTotal);
                    foreach ($columns as $column) {
                        $cDimensions = $column->getDimensions();
                        $cDimensions->setWidth(Math::sub($cDimensions->getWidth(), $toRemove));
                        $column->getFirstChild()->getDimensions()->setWidth($cDimensions->getInnerWidth());
                    }
                    $rowWidth = Math::add($rowWidth, $cDimensions->getWidth());
                }
                $this->setRowsWidth();

                break;
            case 1:
                // minimal stays minimal, decreasing pixels
                $toPixelDisposition = Math::sub($nonPercentageSpace, $autoColumnsMinWidth);
                foreach ($this->pixelColumns as $columnIndex => $columns) {
                    Math::setAccurate(true);
                    $ratio = Math::div($this->preferredWidths[$columnIndex], $totalPixelWidth);
                    $columnWidth = Math::mul($toPixelDisposition, $ratio);
                    Math::setAccurate(false);
                    foreach ($columns as $column) {
                        $columnDimensions = $column->getDimensions();
                        $columnDimensions->setWidth(Math::add($columnWidth, $column->getStyle()->getHorizontalPaddingsWidth()));
                        $column->getFirstChild()->getDimensions()->setWidth($columnDimensions->getInnerWidth());
                    }
                }
                foreach ($this->percentColumns as $columnIndex => $columns) {
                    foreach ($columns as $column) {
                        $columnDimensions = $column->getDimensions();
                        $columnDimensions->setWidth(Math::add($eachPercentagesWidth[$columnIndex], $column->getStyle()->getHorizontalPaddingsWidth()));
                        $column->getFirstChild()->getDimensions()->setWidth($columnDimensions->getInnerWidth());
                    }
                }
                $this->setRowsWidth();

                break;
            case 2:
                // minimal stays minimal, pixels stays untouched, auto columns decreasing
                $toAutoDisposition = Math::sub($nonPercentageSpace, $totalPixelWidth);
                $nonMinWidthColumns = [];
                foreach ($this->autoColumns as $columnIndex => $columns) {
                    Math::setAccurate(true);
                    $ratio = Math::div($this->contentWidths[$columnIndex], $autoColumnsMaxWidth);
                    $columnWidth = Math::mul($toAutoDisposition, $ratio);
                    Math::setAccurate(false);
                    if (Math::comp($this->minWidths[$columnIndex], $columnWidth) > 0) {
                        $toAutoDisposition = Math::sub($toAutoDisposition, Math::sub($this->minWidths[$columnIndex], $columnWidth));
                        $columnWidth = $this->minWidths[$columnIndex];
                        foreach ($columns as $column) {
                            $columnDimensions = $column->getDimensions();
                            $columnDimensions->setWidth(Math::add($columnWidth, $column->getStyle()->getHorizontalPaddingsWidth()));
                            $column->getFirstChild()->getDimensions()->setWidth($columnDimensions->getInnerWidth());
                        }
                    } else {
                        $nonMinWidthColumns[$columnIndex] = $columns;
                    }
                }
                foreach ($nonMinWidthColumns as $columnIndex => $columns) {
                    Math::setAccurate(true);
                    $ratio = Math::div($this->contentWidths[$columnIndex], $autoColumnsMaxWidth);
                    $columnWidth = Math::mul($toAutoDisposition, $ratio);
                    Math::setAccurate(false);
                    foreach ($columns as $column) {
                        $columnDimensions = $column->getDimensions();
                        $columnDimensions->setWidth(Math::add($columnWidth, $column->getStyle()->getHorizontalPaddingsWidth()));
                        $column->getFirstChild()->getDimensions()->setWidth($columnDimensions->getInnerWidth());
                    }
                }
                foreach ($this->percentColumns as $columnIndex => $columns) {
                    foreach ($columns as $column) {
                        $columnDimensions = $column->getDimensions();
                        $columnDimensions->setWidth(Math::add($eachPercentagesWidth[$columnIndex], $column->getStyle()->getHorizontalPaddingsWidth()));
                        $column->getFirstChild()->getDimensions()->setWidth($columnDimensions->getInnerWidth());
                    }
                }
                $this->setRowsWidth();

                break;
        }

        return $this->finish();
    }

    /**
     * Add to others (left space to auto/pixel columns).
     *
     * @param string $leftSpace
     * @param bool   $withPreferred
     *
     * @return $this
     */
    protected function addToOthers(string $leftSpace, bool $withPreferred = false)
    {
        // first of all try to redistribute space to columns that need it most (width is under preferred)
        // left space is the space that we can add to other column types that needs extra space to preferred width
        if ($withPreferred) {
            $leftSpace = $this->addToPreferredOthers($leftSpace);
        }

        // ok, we've redistribute space to columns that needs it but if there is space left we must redistribute it
        // to fulfill percentages
        if (0 === Math::comp($leftSpace, '0')) {
            return $this;
        }
        // first redistribute it to auto columns because they are most flexible ones
        if (!empty($this->autoColumns)) {
            $autoColumnsMaxWidth = $this->getAutoColumnsMaxWidth();
            foreach ($this->autoColumns as $columnIndex => $columns) {
                Math::setAccurate(true);
                $ratio = Math::div($this->contentWidths[$columnIndex], $autoColumnsMaxWidth);
                $add = Math::mul($leftSpace, $ratio);
                Math::setAccurate(false);
                $colWidth = Math::add($columns[0]->getDimensions()->getWidth(), $add);
                foreach ($columns as $column) {
                    $colDmns = $column->getDimensions();
                    $colDmns->setWidth($colWidth);
                    $column->getFirstChild()->getDimensions()->setWidth($colDmns->getInnerWidth());
                }
                if (!$withPreferred) {
                    // if not to preferred it means that we adding to min widths
                    $this->minWidths[$columnIndex] = $colWidth;
                }
            }
        } elseif ($count = \count($this->pixelColumns)) {
            // next redistribute left space to pixel columns if there where no auto columns
            $add = Math::div($leftSpace, (string) $count);
            foreach ($this->pixelColumns as $columnIndex => $columns) {
                $colWidth = Math::add($columns[0]->getDimensions()->getWidth(), $add);
                foreach ($columns as $column) {
                    $colDmns = $column->getDimensions();
                    $colDmns->setWidth($colWidth);
                    $column->getFirstChild()->getDimensions()->setWidth($colDmns->getInnerWidth());
                }
                if (!$withPreferred) {
                    // if not to preferred it means that we adding to min widths
                    $this->minWidths[$columnIndex] = $colWidth;
                }
            }
        }

        return $this;
    }

    /**
     * Try preferred width.
     *
     * @param string $leftSpace
     * @param bool   $outerWidthSet
     *
     * @return $this|TableBox
     */
    protected function tryPreferred(string $leftSpace, bool $outerWidthSet)
    {
        // left space is 100% width that we can use
        $totalPercentages = '0';
        $totalPercentagesWidth = '0';
        foreach ($this->percentages as $columnIndex => $percentage) {
            $totalPercentages = Math::add($totalPercentages, $percentage);
            $colWidth = $this->rows[0]->getChildren()[$columnIndex]->getDimensions()->getInnerWidth();
            $totalPercentagesWidth = Math::add($totalPercentagesWidth, $colWidth);
        }
        $forPercentages = Math::percent($totalPercentages, $leftSpace);
        $neededTotal = '0';
        $needed = [];
        foreach ($this->percentColumns as $columnIndex => $columns) {
            $colDmns = $columns[0]->getDimensions();
            $colWidth = $colDmns->getInnerWidth();
            if (Math::comp($colWidth, $this->contentWidths[$columnIndex]) < 0) {
                $needed[$columnIndex] = Math::sub($this->contentWidths[$columnIndex], $colWidth);
                $neededTotal = Math::add($neededTotal, $needed[$columnIndex]);
            }
        }
        if (0 === Math::comp($neededTotal, '0') && !$outerWidthSet) {
            return $this->setRowsWidth();
        }
        $currentPercentsWidth = '0';
        $addToPercents = Math::min($neededTotal, $forPercentages);
        foreach ($this->percentColumns as $columnIndex => $columns) {
            if (Math::comp($addToPercents, $neededTotal) < 0) {
                Math::setAccurate(true);
                $ratio = Math::div($this->percentages[$columnIndex], $totalPercentages);
                $add = Math::mul($ratio, $addToPercents);
                Math::setAccurate(false);
            } else {
                if (isset($needed[$columnIndex])) {
                    $add = $needed[$columnIndex];
                } else {
                    $add = '0';
                }
            }
            foreach ($columns as $column) {
                $colDmns = $column->getDimensions();
                $colDmns->setWidth(Math::add($colDmns->getWidth(), $add));
                $column->getFirstChild()->getDimensions()->setWidth($colDmns->getInnerWidth());
            }
            $currentPercentsWidth = Math::add($currentPercentsWidth, $colDmns->getInnerWidth());
        }
        // we've added space to percentage columns, now we must calculate how much space we need to add (to have 100%)
        $leftSpace2 = Math::sub($leftSpace, $addToPercents);
        if (0 === Math::comp($leftSpace2, '0')) {
            return $this->setRowsWidth();
        }
        // left space MUST be redistributed to fulfill new percentages
        $this->addToOthers($leftSpace2, true);
        // percent columns were redistributed in the first step so we don't need to do anything
        $this->setRowsWidth();

        return $this;
    }

    /**
     * Get table min width.
     *
     * @return string
     */
    public function getMinWidth()
    {
        foreach ($this->getCells() as $cell) {
            $cell->measureWidth();
        }
        $this->rows = $this->getRows();
        $this->setUpSizingTypes();
        $this->setUpWidths('0');
        $this->minContentGuess($this->rows)->finish();

        return $this->getDimensions()->getWidth();
    }

    /**
     * {@inheritdoc}
     */
    public function measureWidth(bool $afterPageDividing = false)
    {
        if ($this->parentWidth === $this->getParent()->getParent()->getDimensions()->getWidth()) {
            return $this;
        }
        $this->parentWidth = $this->getParent()->getParent()->getDimensions()->getWidth();
        foreach ($this->getCells() as $cell) {
            $cell->measureWidth($afterPageDividing);
        }
        $step = 0;
        $this->setUpSizingTypes();
        $availableSpace = $this->getParent()->getDimensions()->computeAvailableSpace();
        $outerWidthSet = false;
        if ('auto' !== $this->getParent()->getStyle()->getRules('width')) {
            $this->getParent()->applyStyleWidth();
            $availableSpace = Math::min($availableSpace, $this->getParent()->getDimensions()->getInnerWidth());
            $outerWidthSet = true;
        }
        $rows = $this->rows = $this->getRows();
        $this->setUpWidths($availableSpace);
        $this->minContentGuess($rows);
        $this->minContentPercentageGuess($rows, $availableSpace);
        if (!$this->willFit($availableSpace)) {
            return $this->shrinkToFit($availableSpace, $step);
        }
        $step = 1;
        $this->minContentSpecifiedGuess($rows, $availableSpace);
        if (!$this->willFit($availableSpace)) {
            return $this->shrinkToFit($availableSpace, $step);
        }
        $step = 2;
        $this->maxContentGuess($rows, $availableSpace);
        if (!$this->willFit($availableSpace)) {
            return $this->shrinkToFit($availableSpace, $step);
        }
        $currentWidth = $this->getDimensions()->getWidth();
        $leftSpace = Math::sub($availableSpace, $currentWidth);
        if (Math::comp($leftSpace, '0') > 0) {
            $this->tryPreferred($leftSpace, $outerWidthSet);
        }

        return $this->finish();
    }

    /**
     * {@inheritdoc}
     */
    public function measureHeight(bool $afterPageDividing = false)
    {
        if ($this->wasCut()) {
            return $this;
        }
        foreach ($this->getCells() as $cell) {
            $cell->measureHeight($afterPageDividing);
        }
        $style = $this->getStyle();
        $maxRowHeights = [];
        foreach ($this->getChildren() as $rowGroupIndex => $rowGroup) {
            $rows = $rowGroup->getChildren();
            $spannedRowsCount = []; // spannedRowsCount is array of number of row spans for each row
            foreach ($rows as $rowIndex => $row) {
                foreach ($row->getChildren() as $column) {
                    $spannedRowsCount[$rowIndex] = max($spannedRowsCount[$rowIndex] ?? '0', $column->getRowSpan());
                }
            }
            $rowsCount = []; // rowsCount is array with number of rows they must share for each row
            foreach ($spannedRowsCount as $currentRowSpanIndex => $currentRowsCount) {
                for ($i = 0; $i < $currentRowsCount; ++$i) {
                    $rowsCount[$currentRowSpanIndex + $i] = max($rowsCount[$currentRowSpanIndex + $i] ?? '0', $currentRowsCount);
                }
            }
            // get maximal height of each row
            foreach ($rows as $rowIndex => $row) {
                foreach ($row->getChildren() as $column) {
                    $cell = $column->getFirstChild();
                    if (!isset($maxRowHeights[$rowGroupIndex][$rowIndex])) {
                        $maxRowHeights[$rowGroupIndex][$rowIndex] = '0';
                    }
                    $columnStyle = $column->getStyle();
                    $columnVerticalSize = Math::add($columnStyle->getVerticalMarginsWidth(), $columnStyle->getVerticalPaddingsWidth(), $columnStyle->getVerticalBordersWidth());
                    $columnHeight = Math::add($cell->getDimensions()->getOuterHeight(), $columnVerticalSize);
                    // for now ignore height of column that have span greater than 1
                    if (1 === $column->getRowSpan()) {
                        $maxRowHeights[$rowGroupIndex][$rowIndex] = Math::max($maxRowHeights[$rowGroupIndex][$rowIndex], $columnHeight);
                    }
                }
            }
            // column that is spanned with more than 1 row must have height that is equal to all spanned rows height
            foreach ($rows as $rowIndex => $row) {
                $currentRowMax = $maxRowHeights[$rowGroupIndex][$rowIndex] ?? '0';
                foreach ($row->getChildren() as $column) {
                    $rowSpan = $column->getRowSpan();
                    if ($rowSpan > 1) {
                        $spannedRowsHeight = '0';
                        // get sum of spanned row height starting from current row
                        for ($i = 0; $i < $rowSpan; ++$i) {
                            if (isset($maxRowHeights[$rowGroupIndex][$rowIndex + $i])) {
                                $spannedRowsHeight = Math::add($spannedRowsHeight, $maxRowHeights[$rowGroupIndex][$rowIndex + $i]);
                            }
                        }
                        $fromOtherRows = Math::div($spannedRowsHeight, (string) $rowSpan);
                        $fromColumnHeight = Math::div($column->getDimensions()->getOuterHeight(), (string) $rowSpan);
                        $currentRowMax = Math::max($currentRowMax, $fromOtherRows, $fromColumnHeight);
                        // if column that have rowSpan >1 is higher than sum of all other spanned rows max height expand others
                        if (Math::comp($fromColumnHeight, $fromOtherRows) > 0) {
                            for ($i = 0; $i < $rowSpan; ++$i) {
                                $maxRowHeights[$rowGroupIndex][$rowIndex + $i] = $currentRowMax;
                            }
                        }
                    }
                }
                $maxRowHeights[$rowGroupIndex][$rowIndex] = $currentRowMax;
            }
        }
        $tableHeight = '0';
        $rowGroups = $this->getChildren();
        foreach ($rowGroups as $rowGroupIndex => $rowGroup) {
            $rowGroupHeight = '0';
            $rows = $rowGroup->getChildren();
            foreach ($rows as $rowIndex => $row) {
                $rowStyle = $row->getStyle();
                $row->getDimensions()->setHeight(Math::add($maxRowHeights[$rowGroupIndex][$rowIndex], $rowStyle->getVerticalBordersWidth(), $rowStyle->getVerticalPaddingsWidth()));
                $rowGroupHeight = Math::add($rowGroupHeight, $row->getDimensions()->getHeight());
                foreach ($row->getChildren() as $column) {
                    if ($column->getRowSpan() > 1 && $afterPageDividing) {
                        continue;
                    }
                    $column->getDimensions()->setHeight($row->getDimensions()->getInnerHeight());
                    $cell = $column->getFirstChild();
                    $cellStyle = $cell->getStyle();
                    if ('auto' !== $cellStyle->getRules('height')) {
                        $cellStyle->getRules()['height']->convert($cellStyle);
                        $height = $cellStyle->getRules('height');
                    } else {
                        $height = $column->getDimensions()->getInnerHeight();
                        $height = Math::div($height, (string) $column->getRowSpan());
                    }
                    $cellChildrenHeight = '0';
                    foreach ($cell->getChildren() as $cellChild) {
                        $cellChildrenHeight = Math::add($cellChildrenHeight, $cellChild->getDimensions()->getOuterHeight());
                    }
                    $cellVerticalSize = Math::add($cellStyle->getVerticalBordersWidth(), $cellStyle->getVerticalPaddingsWidth());
                    $cellChildrenHeight = Math::add($cellChildrenHeight, $cellVerticalSize);
                    $cellChildrenHeight = Math::div($cellChildrenHeight, (string) $column->getRowSpan());
                    // add vertical padding if needed
                    if (Math::comp($height, $cellChildrenHeight) > 0) {
                        $freeSpace = Math::sub($height, $cellChildrenHeight);
                        $cellStyle = $cell->getStyle();
                        switch ($cellStyle->getRules('vertical-align')) {
                            case 'top':
                                $freeSpace = Math::add($freeSpace, $cellStyle->getRules('padding-bottom'));
                                $cellStyle->setRule('padding-bottom', $freeSpace);
                                break;
                            case 'bottom':
                                $freeSpace = Math::add($freeSpace, $cellStyle->getRules('padding-top'));
                                $cellStyle->setRule('padding-top', $freeSpace);
                                break;
                            case 'baseline':
                            case 'middle':
                            default:
                                $disposition = Math::div($freeSpace, '2');
                                $paddingTop = Math::add($cellStyle->getRules('padding-top'), $disposition);
                                $paddingBottom = Math::add($cellStyle->getRules('padding-bottom'), $disposition);
                                $cellStyle->setRule('padding-top', $paddingTop);
                                $cellStyle->setRule('padding-bottom', $paddingBottom);
                                break;
                        }
                    }
                    $height = Math::max($height, $cellChildrenHeight);
                    $cell->getDimensions()->setHeight($height);
                }
            }
            if (isset($row) && 'separate' === $row->getStyle()->getRules('border-collapse')) {
                $rowGroupHeight = Math::add($rowGroupHeight, $row->getStyle()->getRules('border-spacing'));
            }
            $rowGroup->getDimensions()->setHeight($rowGroupHeight);
            $tableHeight = Math::add($tableHeight, $rowGroupHeight);
        }
        $this->getDimensions()->setHeight(Math::add($tableHeight, $style->getVerticalBordersWidth(), $style->getVerticalPaddingsWidth()));

        return $this;
    }

    /**
     * Remove empty rows.
     *
     * @return $this
     */
    public function removeEmptyRows()
    {
        foreach ($this->getChildren() as $rowGroup) {
            if (!$rowGroup->containContent() || !$rowGroup->hasChildren()) {
                $this->removeChild($rowGroup);
            } else {
                foreach ($rowGroup->getChildren() as $row) {
                    if (!$row->containContent() || !$row->hasChildren()) {
                        $rowGroup->removeChild($row);
                    }
                }
            }
        }

        return $this;
    }
}