Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
93.94% |
31 / 33 |
|
66.67% |
2 / 3 |
CRAP | |
0.00% |
0 / 1 |
| BezierCurve | |
93.94% |
31 / 33 |
|
66.67% |
2 / 3 |
12.03 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| pointAt | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
| bounds | |
92.59% |
25 / 27 |
|
0.00% |
0 / 1 |
10.04 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Phpdftk\Geometry; |
| 6 | |
| 7 | /** |
| 8 | * Cubic Bézier curve — the primitive behind PDF path operators (c, v, y). |
| 9 | * |
| 10 | * Four control points define the curve. `boundingBox()` computes tight |
| 11 | * bounds via derivative roots, used for clipping and hit-testing. |
| 12 | */ |
| 13 | final class BezierCurve |
| 14 | { |
| 15 | public function __construct( |
| 16 | public readonly Point $p0, |
| 17 | public readonly Point $p1, |
| 18 | public readonly Point $p2, |
| 19 | public readonly Point $p3, |
| 20 | ) {} |
| 21 | |
| 22 | public function pointAt(float $t): Point |
| 23 | { |
| 24 | $u = 1 - $t; |
| 25 | return new Point( |
| 26 | $u ** 3 * $this->p0->x + 3 * $u ** 2 * $t * $this->p1->x + 3 * $u * $t ** 2 * $this->p2->x + $t ** 3 * $this->p3->x, |
| 27 | $u ** 3 * $this->p0->y + 3 * $u ** 2 * $t * $this->p1->y + 3 * $u * $t ** 2 * $this->p2->y + $t ** 3 * $this->p3->y, |
| 28 | ); |
| 29 | } |
| 30 | |
| 31 | /** Bounding box via extrema of the derivative polynomial */ |
| 32 | public function bounds(): Rectangle |
| 33 | { |
| 34 | $xs = [$this->p0->x, $this->p3->x]; |
| 35 | $ys = [$this->p0->y, $this->p3->y]; |
| 36 | foreach (['x', 'y'] as $axis) { |
| 37 | $p0 = $this->p0->$axis; |
| 38 | $p1 = $this->p1->$axis; |
| 39 | $p2 = $this->p2->$axis; |
| 40 | $p3 = $this->p3->$axis; |
| 41 | $a = -3 * $p0 + 9 * $p1 - 9 * $p2 + 3 * $p3; |
| 42 | $b = 6 * $p0 - 12 * $p1 + 6 * $p2; |
| 43 | $c = -3 * $p0 + 3 * $p1; |
| 44 | if (abs($a) > 1e-10) { |
| 45 | $disc = $b * $b - 4 * $a * $c; |
| 46 | if ($disc >= 0) { |
| 47 | foreach ([(-$b + sqrt($disc)) / (2 * $a), (-$b - sqrt($disc)) / (2 * $a)] as $t) { |
| 48 | if ($t > 0 && $t < 1) { |
| 49 | $pt = $this->pointAt($t); |
| 50 | ${$axis . 's'}[] = $pt->$axis; |
| 51 | } |
| 52 | } |
| 53 | } |
| 54 | } elseif (abs($b) > 1e-10) { |
| 55 | $t = -$c / $b; |
| 56 | if ($t > 0 && $t < 1) { |
| 57 | $pt = $this->pointAt($t); |
| 58 | ${$axis . 's'}[] = $pt->$axis; |
| 59 | } |
| 60 | } |
| 61 | } |
| 62 | $minX = min($xs); |
| 63 | $maxX = max($xs); |
| 64 | $minY = min($ys); |
| 65 | $maxY = max($ys); |
| 66 | return new Rectangle($minX, $minY, $maxX - $minX, $maxY - $minY); |
| 67 | } |
| 68 | } |