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
12namespace Symfony\Component\Console\Helper;
13
14use Symfony\Component\Console\Exception\InvalidArgumentException;
15use Symfony\Component\Console\Exception\RuntimeException;
16use Symfony\Component\Console\Formatter\OutputFormatter;
17use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface;
18use Symfony\Component\Console\Output\ConsoleSectionOutput;
19use 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 */
30class 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}
814