Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
63 / 63
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
TableRenderer
100.00% covered (success)
100.00%
63 / 63
100.00% covered (success)
100.00%
2 / 2
16
100.00% covered (success)
100.00%
1 / 1
 rowHeight
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 drawRow
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
1 / 1
12
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Pdf\Writer;
6
7use Phpdftk\Pdf\Core\Content\ContentStream;
8
9/**
10 * Renders {@see Table} content into a {@see ContentStream}. The
11 * renderer is stateless and reusable; pagination logic lives at the
12 * call site (`Pdf::addTable` measures each row and starts a new page
13 * when needed).
14 *
15 * Coordinates: `$y` is the y-coordinate of the top edge of the row;
16 * the row is drawn downward and the returned float is the height
17 * consumed.
18 */
19final class TableRenderer
20{
21    /**
22     * Height a single row needs, in points, after greedy word-wrap.
23     *
24     * @param list<string> $row
25     * @param list<float>  $widths
26     */
27    public function rowHeight(array $row, array $widths, TableRenderContext $ctx, bool $isHeader): float
28    {
29        $size = $ctx->fontSize;
30        $padding = $ctx->style->cellPadding;
31        $metrics = $isHeader ? $ctx->headerMetrics : $ctx->bodyMetrics;
32        $font = $isHeader ? $ctx->headerFont : $ctx->bodyFont;
33
34        $maxLines = 1;
35        $colCount = count($widths);
36        for ($i = 0; $i < $colCount; $i++) {
37            $cell = $row[$i] ?? '';
38            $innerW = max(0.0, $widths[$i] - 2.0 * $padding);
39            $encoded = $font->getTextEncoder()?->encode($cell) ?? $cell;
40            $lines = TextLayout::wrap($encoded, $metrics, $size, $innerW);
41            $maxLines = max($maxLines, count($lines));
42        }
43
44        return $maxLines * $size * $ctx->lineHeight + 2.0 * $padding;
45    }
46
47    /**
48     * Draw one row at (x, y). Returns the height consumed so the
49     * caller can advance its cursor.
50     *
51     * @param list<string> $row
52     * @param list<float>  $widths
53     */
54    public function drawRow(
55        ContentStream $cs,
56        float $x,
57        float $y,
58        array $row,
59        array $widths,
60        TableRenderContext $ctx,
61        bool $isHeader,
62    ): float {
63        $style = $ctx->style;
64        $size = $ctx->fontSize;
65        $padding = $style->cellPadding;
66        $lineHeight = $size * $ctx->lineHeight;
67        $font = $isHeader ? $ctx->headerFont : $ctx->bodyFont;
68        $metrics = $isHeader ? $ctx->headerMetrics : $ctx->bodyMetrics;
69
70        $rowH = $this->rowHeight($row, $widths, $ctx, $isHeader);
71        $totalWidth = array_sum($widths);
72
73        $cs->saveGraphicsState();
74
75        // Header background fill, drawn before borders so the lines sit on top.
76        if ($isHeader && $style->headerBgColor !== null) {
77            [$r, $g, $b] = $style->headerBgColor;
78            $cs->setFillColorRGB($r, $g, $b)
79               ->rectangle($x, $y - $rowH, $totalWidth, $rowH)
80               ->fill();
81        }
82
83        // Borders (cells outline + outer frame).
84        if ($style->borderWidth > 0.0) {
85            [$br, $bg, $bb] = $style->borderColor;
86            $cs->setStrokeColorRGB($br, $bg, $bb)->setLineWidth($style->borderWidth);
87
88            // Top and bottom of the row.
89            $cs->moveTo($x, $y)->lineTo($x + $totalWidth, $y)->stroke();
90            $cs->moveTo($x, $y - $rowH)->lineTo($x + $totalWidth, $y - $rowH)->stroke();
91
92            // Vertical column dividers (left edge of every cell + right edge of last cell).
93            $cx = $x;
94            foreach ($widths as $w) {
95                $cs->moveTo($cx, $y)->lineTo($cx, $y - $rowH)->stroke();
96                $cx += $w;
97            }
98            $cs->moveTo($cx, $y)->lineTo($cx, $y - $rowH)->stroke();
99        }
100
101        // Cell content.
102        $cs->setFillColorRGB(0.0, 0.0, 0.0);
103        $colX = $x;
104        $colCount = count($widths);
105        for ($i = 0; $i < $colCount; $i++) {
106            $cell = $row[$i] ?? '';
107            $colW = $widths[$i];
108            $innerW = max(0.0, $colW - 2.0 * $padding);
109
110            $encoded = $font->getTextEncoder()?->encode($cell) ?? $cell;
111            $lines = TextLayout::wrap($encoded, $metrics, $size, $innerW);
112            $align = $style->alignmentFor($i);
113
114            $textTop = $y - $padding;
115            foreach ($lines as $lineIdx => $line) {
116                $lineWidth = TextLayout::measure($line, $metrics, $size);
117                $tx = $colX + $padding + match ($align) {
118                    Alignment::Left   => 0.0,
119                    Alignment::Center => ($innerW - $lineWidth) / 2.0,
120                    Alignment::Right  => $innerW - $lineWidth,
121                };
122                $ty = $textTop - ($lineIdx + 1) * $lineHeight + ($lineHeight - $size) / 2.0;
123                $cs->beginText()
124                   ->setFont($font->getResourceName(), $size)
125                   ->moveTextPosition($tx, $ty)
126                   ->showText($line)
127                   ->endText();
128            }
129
130            $colX += $colW;
131        }
132
133        $cs->restoreGraphicsState();
134        return $rowH;
135    }
136}