Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.94% covered (success)
93.94%
31 / 33
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
BezierCurve
93.94% covered (success)
93.94%
31 / 33
66.67% covered (warning)
66.67%
2 / 3
12.03
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 pointAt
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 bounds
92.59% covered (success)
92.59%
25 / 27
0.00% covered (danger)
0.00%
0 / 1
10.04
1<?php
2
3declare(strict_types=1);
4
5namespace 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 */
13final 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}