Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.14% covered (success)
97.14%
34 / 35
87.50% covered (warning)
87.50%
7 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
PathPainterState
97.14% covered (success)
97.14%
34 / 35
87.50% covered (warning)
87.50%
7 / 8
12
0.00% covered (danger)
0.00%
0 / 1
 moveTo
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 lineTo
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 clearControlPoints
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 recordCubicControl
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 recordQuadraticControl
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 reflectedCubicControl
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 reflectedQuadraticControl
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 closeSubpath
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\SvgToPdf\Path;
6
7/**
8 * Mutable per-path state for the `<path>` painter. Tracks the SVG cursor
9 * (`x` / `y`), the start of the current subpath (so `Z` knows where to
10 * close back to), and the last cubic / quadratic control point so the
11 * smooth-curve commands (`S` / `T`) can reflect it.
12 *
13 * Internal to `phpdftk/svg-to-pdf` โ€” exists in its own namespace so the
14 * `Translator` stays focused on dispatch.
15 */
16final class PathPainterState
17{
18    public float $currentX = 0.0;
19    public float $currentY = 0.0;
20    public float $subpathStartX = 0.0;
21    public float $subpathStartY = 0.0;
22    public ?float $lastCubicControlX = null;
23    public ?float $lastCubicControlY = null;
24    public ?float $lastQuadraticControlX = null;
25    public ?float $lastQuadraticControlY = null;
26
27    public function moveTo(float $x, float $y): void
28    {
29        $this->currentX = $x;
30        $this->currentY = $y;
31        $this->subpathStartX = $x;
32        $this->subpathStartY = $y;
33        $this->clearControlPoints();
34    }
35
36    public function lineTo(float $x, float $y): void
37    {
38        $this->currentX = $x;
39        $this->currentY = $y;
40        $this->clearControlPoints();
41    }
42
43    public function clearControlPoints(): void
44    {
45        $this->lastCubicControlX = null;
46        $this->lastCubicControlY = null;
47        $this->lastQuadraticControlX = null;
48        $this->lastQuadraticControlY = null;
49    }
50
51    /**
52     * After a cubic curve, the last control point (the one that would
53     * be reflected by a following `S`) is the second control point.
54     */
55    public function recordCubicControl(float $x, float $y): void
56    {
57        $this->lastCubicControlX = $x;
58        $this->lastCubicControlY = $y;
59        $this->lastQuadraticControlX = null;
60        $this->lastQuadraticControlY = null;
61    }
62
63    public function recordQuadraticControl(float $x, float $y): void
64    {
65        $this->lastQuadraticControlX = $x;
66        $this->lastQuadraticControlY = $y;
67        $this->lastCubicControlX = null;
68        $this->lastCubicControlY = null;
69    }
70
71    /**
72     * Reflection of the last cubic control point about the current
73     * point โ€” used by `S` to synthesise its first control point. Falls
74     * back to the current point when the previous command wasn't cubic
75     * per SVG 2 ยง9.3.7.
76     *
77     * @return array{float, float}
78     */
79    public function reflectedCubicControl(): array
80    {
81        if ($this->lastCubicControlX === null || $this->lastCubicControlY === null) {
82            return [$this->currentX, $this->currentY];
83        }
84        return [
85            2.0 * $this->currentX - $this->lastCubicControlX,
86            2.0 * $this->currentY - $this->lastCubicControlY,
87        ];
88    }
89
90    /**
91     * Reflection of the last quadratic control point โ€” used by `T`.
92     *
93     * @return array{float, float}
94     */
95    public function reflectedQuadraticControl(): array
96    {
97        if ($this->lastQuadraticControlX === null || $this->lastQuadraticControlY === null) {
98            return [$this->currentX, $this->currentY];
99        }
100        return [
101            2.0 * $this->currentX - $this->lastQuadraticControlX,
102            2.0 * $this->currentY - $this->lastQuadraticControlY,
103        ];
104    }
105
106    public function closeSubpath(): void
107    {
108        $this->currentX = $this->subpathStartX;
109        $this->currentY = $this->subpathStartY;
110        $this->clearControlPoints();
111    }
112}