"Fossies" - the Fresh Open Source Software Archive

Member "grav/vendor/symfony/console/Helper/Table.php" (1 Sep 2020, 25925 Bytes) of package /linux/www/grav-v1.6.27.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Table.php" see the Fossies "Dox" file reference documentation.

    1 <?php
    2 
    3 /*
    4  * This file is part of the Symfony package.
    5  *
    6  * (c) Fabien Potencier <fabien@symfony.com>
    7  *
    8  * For the full copyright and license information, please view the LICENSE
    9  * file that was distributed with this source code.
   10  */
   11 
   12 namespace Symfony\Component\Console\Helper;
   13 
   14 use Symfony\Component\Console\Exception\InvalidArgumentException;
   15 use Symfony\Component\Console\Exception\RuntimeException;
   16 use Symfony\Component\Console\Formatter\OutputFormatter;
   17 use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface;
   18 use Symfony\Component\Console\Output\ConsoleSectionOutput;
   19 use Symfony\Component\Console\Output\OutputInterface;
   20 
   21 /**
   22  * Provides helpers to display a table.
   23  *
   24  * @author Fabien Potencier <fabien@symfony.com>
   25  * @author Саша Стаменковић <umpirsky@gmail.com>
   26  * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
   27  * @author Max Grigorian <maxakawizard@gmail.com>
   28  * @author Dany Maillard <danymaillard93b@gmail.com>
   29  */
   30 class Table
   31 {
   32     private const SEPARATOR_TOP = 0;
   33     private const SEPARATOR_TOP_BOTTOM = 1;
   34     private const SEPARATOR_MID = 2;
   35     private const SEPARATOR_BOTTOM = 3;
   36     private const BORDER_OUTSIDE = 0;
   37     private const BORDER_INSIDE = 1;
   38 
   39     private $headerTitle;
   40     private $footerTitle;
   41 
   42     /**
   43      * Table headers.
   44      */
   45     private $headers = [];
   46 
   47     /**
   48      * Table rows.
   49      */
   50     private $rows = [];
   51 
   52     /**
   53      * Column widths cache.
   54      */
   55     private $effectiveColumnWidths = [];
   56 
   57     /**
   58      * Number of columns cache.
   59      *
   60      * @var int
   61      */
   62     private $numberOfColumns;
   63 
   64     /**
   65      * @var OutputInterface
   66      */
   67     private $output;
   68 
   69     /**
   70      * @var TableStyle
   71      */
   72     private $style;
   73 
   74     /**
   75      * @var array
   76      */
   77     private $columnStyles = [];
   78 
   79     /**
   80      * User set column widths.
   81      *
   82      * @var array
   83      */
   84     private $columnWidths = [];
   85     private $columnMaxWidths = [];
   86 
   87     private static $styles;
   88 
   89     private $rendered = false;
   90 
   91     public function __construct(OutputInterface $output)
   92     {
   93         $this->output = $output;
   94 
   95         if (!self::$styles) {
   96             self::$styles = self::initStyles();
   97         }
   98 
   99         $this->setStyle('default');
  100     }
  101 
  102     /**
  103      * Sets a style definition.
  104      *
  105      * @param string     $name  The style name
  106      * @param TableStyle $style A TableStyle instance
  107      */
  108     public static function setStyleDefinition($name, TableStyle $style)
  109     {
  110         if (!self::$styles) {
  111             self::$styles = self::initStyles();
  112         }
  113 
  114         self::$styles[$name] = $style;
  115     }
  116 
  117     /**
  118      * Gets a style definition by name.
  119      *
  120      * @param string $name The style name
  121      *
  122      * @return TableStyle
  123      */
  124     public static function getStyleDefinition($name)
  125     {
  126         if (!self::$styles) {
  127             self::$styles = self::initStyles();
  128         }
  129 
  130         if (isset(self::$styles[$name])) {
  131             return self::$styles[$name];
  132         }
  133 
  134         throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
  135     }
  136 
  137     /**
  138      * Sets table style.
  139      *
  140      * @param TableStyle|string $name The style name or a TableStyle instance
  141      *
  142      * @return $this
  143      */
  144     public function setStyle($name)
  145     {
  146         $this->style = $this->resolveStyle($name);
  147 
  148         return $this;
  149     }
  150 
  151     /**
  152      * Gets the current table style.
  153      *
  154      * @return TableStyle
  155      */
  156     public function getStyle()
  157     {
  158         return $this->style;
  159     }
  160 
  161     /**
  162      * Sets table column style.
  163      *
  164      * @param int               $columnIndex Column index
  165      * @param TableStyle|string $name        The style name or a TableStyle instance
  166      *
  167      * @return $this
  168      */
  169     public function setColumnStyle($columnIndex, $name)
  170     {
  171         $columnIndex = (int) $columnIndex;
  172 
  173         $this->columnStyles[$columnIndex] = $this->resolveStyle($name);
  174 
  175         return $this;
  176     }
  177 
  178     /**
  179      * Gets the current style for a column.
  180      *
  181      * If style was not set, it returns the global table style.
  182      *
  183      * @param int $columnIndex Column index
  184      *
  185      * @return TableStyle
  186      */
  187     public function getColumnStyle($columnIndex)
  188     {
  189         return $this->columnStyles[$columnIndex] ?? $this->getStyle();
  190     }
  191 
  192     /**
  193      * Sets the minimum width of a column.
  194      *
  195      * @param int $columnIndex Column index
  196      * @param int $width       Minimum column width in characters
  197      *
  198      * @return $this
  199      */
  200     public function setColumnWidth($columnIndex, $width)
  201     {
  202         $this->columnWidths[(int) $columnIndex] = (int) $width;
  203 
  204         return $this;
  205     }
  206 
  207     /**
  208      * Sets the minimum width of all columns.
  209      *
  210      * @param array $widths
  211      *
  212      * @return $this
  213      */
  214     public function setColumnWidths(array $widths)
  215     {
  216         $this->columnWidths = [];
  217         foreach ($widths as $index => $width) {
  218             $this->setColumnWidth($index, $width);
  219         }
  220 
  221         return $this;
  222     }
  223 
  224     /**
  225      * Sets the maximum width of a column.
  226      *
  227      * Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while
  228      * formatted strings are preserved.
  229      *
  230      * @return $this
  231      */
  232     public function setColumnMaxWidth(int $columnIndex, int $width): self
  233     {
  234         if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) {
  235             throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, \get_class($this->output->getFormatter())));
  236         }
  237 
  238         $this->columnMaxWidths[$columnIndex] = $width;
  239 
  240         return $this;
  241     }
  242 
  243     public function setHeaders(array $headers)
  244     {
  245         $headers = array_values($headers);
  246         if (!empty($headers) && !\is_array($headers[0])) {
  247             $headers = [$headers];
  248         }
  249 
  250         $this->headers = $headers;
  251 
  252         return $this;
  253     }
  254 
  255     public function setRows(array $rows)
  256     {
  257         $this->rows = [];
  258 
  259         return $this->addRows($rows);
  260     }
  261 
  262     public function addRows(array $rows)
  263     {
  264         foreach ($rows as $row) {
  265             $this->addRow($row);
  266         }
  267 
  268         return $this;
  269     }
  270 
  271     public function addRow($row)
  272     {
  273         if ($row instanceof TableSeparator) {
  274             $this->rows[] = $row;
  275 
  276             return $this;
  277         }
  278 
  279         if (!\is_array($row)) {
  280             throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.');
  281         }
  282 
  283         $this->rows[] = array_values($row);
  284 
  285         return $this;
  286     }
  287 
  288     /**
  289      * Adds a row to the table, and re-renders the table.
  290      */
  291     public function appendRow($row): self
  292     {
  293         if (!$this->output instanceof ConsoleSectionOutput) {
  294             throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
  295         }
  296 
  297         if ($this->rendered) {
  298             $this->output->clear($this->calculateRowCount());
  299         }
  300 
  301         $this->addRow($row);
  302         $this->render();
  303 
  304         return $this;
  305     }
  306 
  307     public function setRow($column, array $row)
  308     {
  309         $this->rows[$column] = $row;
  310 
  311         return $this;
  312     }
  313 
  314     public function setHeaderTitle(?string $title): self
  315     {
  316         $this->headerTitle = $title;
  317 
  318         return $this;
  319     }
  320 
  321     public function setFooterTitle(?string $title): self
  322     {
  323         $this->footerTitle = $title;
  324 
  325         return $this;
  326     }
  327 
  328     /**
  329      * Renders table to output.
  330      *
  331      * Example:
  332      *
  333      *     +---------------+-----------------------+------------------+
  334      *     | ISBN          | Title                 | Author           |
  335      *     +---------------+-----------------------+------------------+
  336      *     | 99921-58-10-7 | Divine Comedy         | Dante Alighieri  |
  337      *     | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
  338      *     | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
  339      *     +---------------+-----------------------+------------------+
  340      */
  341     public function render()
  342     {
  343         $rows = array_merge($this->headers, [$divider = new TableSeparator()], $this->rows);
  344         $this->calculateNumberOfColumns($rows);
  345 
  346         $rows = $this->buildTableRows($rows);
  347         $this->calculateColumnsWidth($rows);
  348 
  349         $isHeader = true;
  350         $isFirstRow = false;
  351         foreach ($rows as $row) {
  352             if ($divider === $row) {
  353                 $isHeader = false;
  354                 $isFirstRow = true;
  355 
  356                 continue;
  357             }
  358             if ($row instanceof TableSeparator) {
  359                 $this->renderRowSeparator();
  360 
  361                 continue;
  362             }
  363             if (!$row) {
  364                 continue;
  365             }
  366 
  367             if ($isHeader || $isFirstRow) {
  368                 if ($isFirstRow) {
  369                     $this->renderRowSeparator(self::SEPARATOR_TOP_BOTTOM);
  370                     $isFirstRow = false;
  371                 } else {
  372                     $this->renderRowSeparator(self::SEPARATOR_TOP, $this->headerTitle, $this->style->getHeaderTitleFormat());
  373                 }
  374             }
  375 
  376             $this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
  377         }
  378         $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat());
  379 
  380         $this->cleanup();
  381         $this->rendered = true;
  382     }
  383 
  384     /**
  385      * Renders horizontal header separator.
  386      *
  387      * Example:
  388      *
  389      *     +-----+-----------+-------+
  390      */
  391     private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null)
  392     {
  393         if (0 === $count = $this->numberOfColumns) {
  394             return;
  395         }
  396 
  397         $borders = $this->style->getBorderChars();
  398         if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) {
  399             return;
  400         }
  401 
  402         $crossings = $this->style->getCrossingChars();
  403         if (self::SEPARATOR_MID === $type) {
  404             list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[2], $crossings[8], $crossings[0], $crossings[4]];
  405         } elseif (self::SEPARATOR_TOP === $type) {
  406             list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[0], $crossings[1], $crossings[2], $crossings[3]];
  407         } elseif (self::SEPARATOR_TOP_BOTTOM === $type) {
  408             list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[0], $crossings[9], $crossings[10], $crossings[11]];
  409         } else {
  410             list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[0], $crossings[7], $crossings[6], $crossings[5]];
  411         }
  412 
  413         $markup = $leftChar;
  414         for ($column = 0; $column < $count; ++$column) {
  415             $markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]);
  416             $markup .= $column === $count - 1 ? $rightChar : $midChar;
  417         }
  418 
  419         if (null !== $title) {
  420             $titleLength = Helper::strlenWithoutDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title));
  421             $markupLength = Helper::strlen($markup);
  422             if ($titleLength > $limit = $markupLength - 4) {
  423                 $titleLength = $limit;
  424                 $formatLength = Helper::strlenWithoutDecoration($formatter, sprintf($titleFormat, ''));
  425                 $formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
  426             }
  427 
  428             $titleStart = ($markupLength - $titleLength) / 2;
  429             if (false === mb_detect_encoding($markup, null, true)) {
  430                 $markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength);
  431             } else {
  432                 $markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength);
  433             }
  434         }
  435 
  436         $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
  437     }
  438 
  439     /**
  440      * Renders vertical column separator.
  441      */
  442     private function renderColumnSeparator($type = self::BORDER_OUTSIDE)
  443     {
  444         $borders = $this->style->getBorderChars();
  445 
  446         return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]);
  447     }
  448 
  449     /**
  450      * Renders table row.
  451      *
  452      * Example:
  453      *
  454      *     | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
  455      */
  456     private function renderRow(array $row, string $cellFormat)
  457     {
  458         $rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
  459         $columns = $this->getRowColumns($row);
  460         $last = \count($columns) - 1;
  461         foreach ($columns as $i => $column) {
  462             $rowContent .= $this->renderCell($row, $column, $cellFormat);
  463             $rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE);
  464         }
  465         $this->output->writeln($rowContent);
  466     }
  467 
  468     /**
  469      * Renders table cell with padding.
  470      */
  471     private function renderCell(array $row, int $column, string $cellFormat)
  472     {
  473         $cell = isset($row[$column]) ? $row[$column] : '';
  474         $width = $this->effectiveColumnWidths[$column];
  475         if ($cell instanceof TableCell && $cell->getColspan() > 1) {
  476             // add the width of the following columns(numbers of colspan).
  477             foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) {
  478                 $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn];
  479             }
  480         }
  481 
  482         // str_pad won't work properly with multi-byte strings, we need to fix the padding
  483         if (false !== $encoding = mb_detect_encoding($cell, null, true)) {
  484             $width += \strlen($cell) - mb_strwidth($cell, $encoding);
  485         }
  486 
  487         $style = $this->getColumnStyle($column);
  488 
  489         if ($cell instanceof TableSeparator) {
  490             return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width));
  491         }
  492 
  493         $width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
  494         $content = sprintf($style->getCellRowContentFormat(), $cell);
  495 
  496         return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $style->getPadType()));
  497     }
  498 
  499     /**
  500      * Calculate number of columns for this table.
  501      */
  502     private function calculateNumberOfColumns($rows)
  503     {
  504         $columns = [0];
  505         foreach ($rows as $row) {
  506             if ($row instanceof TableSeparator) {
  507                 continue;
  508             }
  509 
  510             $columns[] = $this->getNumberOfColumns($row);
  511         }
  512 
  513         $this->numberOfColumns = max($columns);
  514     }
  515 
  516     private function buildTableRows($rows)
  517     {
  518         /** @var WrappableOutputFormatterInterface $formatter */
  519         $formatter = $this->output->getFormatter();
  520         $unmergedRows = [];
  521         for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) {
  522             $rows = $this->fillNextRows($rows, $rowKey);
  523 
  524             // Remove any new line breaks and replace it with a new line
  525             foreach ($rows[$rowKey] as $column => $cell) {
  526                 $colspan = $cell instanceof TableCell ? $cell->getColspan() : 1;
  527 
  528                 if (isset($this->columnMaxWidths[$column]) && Helper::strlenWithoutDecoration($formatter, $cell) > $this->columnMaxWidths[$column]) {
  529                     $cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan);
  530                 }
  531                 if (!strstr($cell, "\n")) {
  532                     continue;
  533                 }
  534                 $escaped = implode("\n", array_map([OutputFormatter::class, 'escapeTrailingBackslash'], explode("\n", $cell)));
  535                 $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped;
  536                 $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
  537                 foreach ($lines as $lineKey => $line) {
  538                     if ($colspan > 1) {
  539                         $line = new TableCell($line, ['colspan' => $colspan]);
  540                     }
  541                     if (0 === $lineKey) {
  542                         $rows[$rowKey][$column] = $line;
  543                     } else {
  544                         $unmergedRows[$rowKey][$lineKey][$column] = $line;
  545                     }
  546                 }
  547             }
  548         }
  549 
  550         return new TableRows(function () use ($rows, $unmergedRows) {
  551             foreach ($rows as $rowKey => $row) {
  552                 yield $this->fillCells($row);
  553 
  554                 if (isset($unmergedRows[$rowKey])) {
  555                     foreach ($unmergedRows[$rowKey] as $row) {
  556                         yield $row;
  557                     }
  558                 }
  559             }
  560         });
  561     }
  562 
  563     private function calculateRowCount(): int
  564     {
  565         $numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows))));
  566 
  567         if ($this->headers) {
  568             ++$numberOfRows; // Add row for header separator
  569         }
  570 
  571         ++$numberOfRows; // Add row for footer separator
  572 
  573         return $numberOfRows;
  574     }
  575 
  576     /**
  577      * fill rows that contains rowspan > 1.
  578      *
  579      * @throws InvalidArgumentException
  580      */
  581     private function fillNextRows(array $rows, int $line): array
  582     {
  583         $unmergedRows = [];
  584         foreach ($rows[$line] as $column => $cell) {
  585             if (null !== $cell && !$cell instanceof TableCell && !is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) {
  586                 throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing __toString, %s given.', \gettype($cell)));
  587             }
  588             if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
  589                 $nbLines = $cell->getRowspan() - 1;
  590                 $lines = [$cell];
  591                 if (strstr($cell, "\n")) {
  592                     $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
  593                     $nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines;
  594 
  595                     $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan()]);
  596                     unset($lines[0]);
  597                 }
  598 
  599                 // create a two dimensional array (rowspan x colspan)
  600                 $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows);
  601                 foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
  602                     $value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : '';
  603                     $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan()]);
  604                     if ($nbLines === $unmergedRowKey - $line) {
  605                         break;
  606                     }
  607                 }
  608             }
  609         }
  610 
  611         foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
  612             // we need to know if $unmergedRow will be merged or inserted into $rows
  613             if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) {
  614                 foreach ($unmergedRow as $cellKey => $cell) {
  615                     // insert cell into row at cellKey position
  616                     array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]);
  617                 }
  618             } else {
  619                 $row = $this->copyRow($rows, $unmergedRowKey - 1);
  620                 foreach ($unmergedRow as $column => $cell) {
  621                     if (!empty($cell)) {
  622                         $row[$column] = $unmergedRow[$column];
  623                     }
  624                 }
  625                 array_splice($rows, $unmergedRowKey, 0, [$row]);
  626             }
  627         }
  628 
  629         return $rows;
  630     }
  631 
  632     /**
  633      * fill cells for a row that contains colspan > 1.
  634      */
  635     private function fillCells($row)
  636     {
  637         $newRow = [];
  638         foreach ($row as $column => $cell) {
  639             $newRow[] = $cell;
  640             if ($cell instanceof TableCell && $cell->getColspan() > 1) {
  641                 foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) {
  642                     // insert empty value at column position
  643                     $newRow[] = '';
  644                 }
  645             }
  646         }
  647 
  648         return $newRow ?: $row;
  649     }
  650 
  651     private function copyRow(array $rows, int $line): array
  652     {
  653         $row = $rows[$line];
  654         foreach ($row as $cellKey => $cellValue) {
  655             $row[$cellKey] = '';
  656             if ($cellValue instanceof TableCell) {
  657                 $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]);
  658             }
  659         }
  660 
  661         return $row;
  662     }
  663 
  664     /**
  665      * Gets number of columns by row.
  666      */
  667     private function getNumberOfColumns(array $row): int
  668     {
  669         $columns = \count($row);
  670         foreach ($row as $column) {
  671             $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0;
  672         }
  673 
  674         return $columns;
  675     }
  676 
  677     /**
  678      * Gets list of columns for the given row.
  679      */
  680     private function getRowColumns(array $row): array
  681     {
  682         $columns = range(0, $this->numberOfColumns - 1);
  683         foreach ($row as $cellKey => $cell) {
  684             if ($cell instanceof TableCell && $cell->getColspan() > 1) {
  685                 // exclude grouped columns.
  686                 $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1));
  687             }
  688         }
  689 
  690         return $columns;
  691     }
  692 
  693     /**
  694      * Calculates columns widths.
  695      */
  696     private function calculateColumnsWidth(iterable $rows)
  697     {
  698         for ($column = 0; $column < $this->numberOfColumns; ++$column) {
  699             $lengths = [];
  700             foreach ($rows as $row) {
  701                 if ($row instanceof TableSeparator) {
  702                     continue;
  703                 }
  704 
  705                 foreach ($row as $i => $cell) {
  706                     if ($cell instanceof TableCell) {
  707                         $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
  708                         $textLength = Helper::strlen($textContent);
  709                         if ($textLength > 0) {
  710                             $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan()));
  711                             foreach ($contentColumns as $position => $content) {
  712                                 $row[$i + $position] = $content;
  713                             }
  714                         }
  715                     }
  716                 }
  717 
  718                 $lengths[] = $this->getCellWidth($row, $column);
  719             }
  720 
  721             $this->effectiveColumnWidths[$column] = max($lengths) + Helper::strlen($this->style->getCellRowContentFormat()) - 2;
  722         }
  723     }
  724 
  725     private function getColumnSeparatorWidth(): int
  726     {
  727         return Helper::strlen(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
  728     }
  729 
  730     private function getCellWidth(array $row, int $column): int
  731     {
  732         $cellWidth = 0;
  733 
  734         if (isset($row[$column])) {
  735             $cell = $row[$column];
  736             $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
  737         }
  738 
  739         $columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0;
  740         $cellWidth = max($cellWidth, $columnWidth);
  741 
  742         return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth;
  743     }
  744 
  745     /**
  746      * Called after rendering to cleanup cache data.
  747      */
  748     private function cleanup()
  749     {
  750         $this->effectiveColumnWidths = [];
  751         $this->numberOfColumns = null;
  752     }
  753 
  754     private static function initStyles()
  755     {
  756         $borderless = new TableStyle();
  757         $borderless
  758             ->setHorizontalBorderChars('=')
  759             ->setVerticalBorderChars(' ')
  760             ->setDefaultCrossingChar(' ')
  761         ;
  762 
  763         $compact = new TableStyle();
  764         $compact
  765             ->setHorizontalBorderChars('')
  766             ->setVerticalBorderChars(' ')
  767             ->setDefaultCrossingChar('')
  768             ->setCellRowContentFormat('%s')
  769         ;
  770 
  771         $styleGuide = new TableStyle();
  772         $styleGuide
  773             ->setHorizontalBorderChars('-')
  774             ->setVerticalBorderChars(' ')
  775             ->setDefaultCrossingChar(' ')
  776             ->setCellHeaderFormat('%s')
  777         ;
  778 
  779         $box = (new TableStyle())
  780             ->setHorizontalBorderChars('─')
  781             ->setVerticalBorderChars('│')
  782             ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├')
  783         ;
  784 
  785         $boxDouble = (new TableStyle())
  786             ->setHorizontalBorderChars('═', '─')
  787             ->setVerticalBorderChars('║', '│')
  788             ->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣')
  789         ;
  790 
  791         return [
  792             'default' => new TableStyle(),
  793             'borderless' => $borderless,
  794             'compact' => $compact,
  795             'symfony-style-guide' => $styleGuide,
  796             'box' => $box,
  797             'box-double' => $boxDouble,
  798         ];
  799     }
  800 
  801     private function resolveStyle($name)
  802     {
  803         if ($name instanceof TableStyle) {
  804             return $name;
  805         }
  806 
  807         if (isset(self::$styles[$name])) {
  808             return self::$styles[$name];
  809         }
  810 
  811         throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
  812     }
  813 }