Completed
Branch master (32b148)
by Edgar
03:11
created

Path::curveTo()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 6
rs 9.4285
cc 2
eloc 3
nc 1
nop 7
1
<?php
2
namespace nstdio\svg\shape;
3
4
use nstdio\svg\container\ContainerInterface;
5
use nstdio\svg\ElementInterface;
6
use nstdio\svg\traits\ElementTrait;
7
use nstdio\svg\util\Bezier;
8
9
/**
10
 * Class Path
11
 *
12
 * @property string $d This attribute defines a path to follow. {@link
13
 *           https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d}
14
 * @package shape
15
 * @author  Edgar Asatryan <[email protected]>
16
 */
17
class Path extends Shape implements ContainerInterface
18
{
19
    use ElementTrait;
20
21
    /**
22
     * The path points positions
23
     *
24
     * @var array
25
     */
26
    public $data;
27
28
    /**
29
     * Path constructor.
30
     *
31
     * @param ElementInterface $parent
32
     * @param float            $x
33
     * @param float            $y
34
     * @param bool             $absolute
35
     */
36
    public function __construct(ElementInterface $parent, $x, $y, $absolute = true)
37
    {
38
        parent::__construct($parent);
39
40
        $this->moveTo($x, $y, $absolute);
41
    }
42
43
    /**
44
     * Start a new sub-path at the given (x,y) coordinate. M (uppercase) indicates that absolute coordinates will
45
     * follow; m (lowercase) indicates that relative coordinates will follow. If a moveto is followed by multiple pairs
46
     * of coordinates, the subsequent pairs are treated as implicit lineto commands. Hence, implicit lineto commands
47
     * will be relative if the moveto is relative, and absolute if the moveto is absolute. If a relative moveto (m)
48
     * appears as the first element of the path, then it is treated as a pair of absolute coordinates. In this case,
49
     * subsequent pairs of coordinates are treated as relative even though the initial moveto is interpreted as an
50
     * absolute moveto.
51
     *
52
     * @link https://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands
53
     *
54
     * @param float $x The absolute (relative) X coordinate for the end point of this path segment.
55
     * @param float $y The absolute (relative) Y coordinate for the end point of this path segment.
56
     * @param bool  $absolute
57
     *
58
     * @return $this
59
     */
60
    public function moveTo($x, $y, $absolute = true)
61
    {
62
        $this->buildPath($absolute ? 'M' : 'm', $x, $y);
63
64
        return $this;
65
    }
66
67
    /**
68
     * @param string $type
69
     * @param mixed  $params
70
     */
71
    private function buildPath($type, $params = null)
0 ignored issues
show
Unused Code introduced by
The parameter $params is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
72
    {
73
        if ($type === 'M' && $this->d !== null) {
74
            throw new \InvalidArgumentException("First modifier for path must be: M");
75
        }
76
        $params = array_slice(func_get_args(), 1);
77
        $this->addData($type, $params);
78
        if ($this->d !== null) {
79
            $this->d .= " $type";
80
            foreach ($params as $key => $value) {
81
                if (is_array($value)) {
82
                    foreach ($value as $item) {
83
                        $this->d .= " $item,";
84
                    }
85
                    $this->d = rtrim($this->d, ',');
86
                } else {
87
                    if ($key % 2 !== 0 && isset($params[$key - 1]) && !is_array($params[$key - 1])) {
88
                        $this->d .= ", $value";
89
                    } else {
90
                        $this->d .= " $value";
91
                    }
92
                }
93
            }
94
        } else {
95
            $this->d = "$type $params[0], $params[1]";
96
        }
97
    }
98
99
    private function addData($type, array $params)
100
    {
101
        $this->data[] = [$type => $params];
102
    }
103
104
    /**
105
     * Draw a line from the current point to the given (x,y) coordinate which becomes the new current point. L
106
     * (uppercase) indicates that absolute coordinates will follow; l (lowercase) indicates that relative coordinates
107
     * will follow. A number of coordinates pairs may be specified to draw a polyline. At the end of the command, the
108
     * new current point is set to the final set of coordinates provided.
109
     *
110
     * @link https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
111
     *
112
     * @param float $x The absolute (relative) X coordinate for the end point of this path segment.
113
     * @param float $y The absolute (relative) Y coordinate for the end point of this path segment.
114
     * @param bool  $absolute
115
     *
116
     * @return $this
117
     */
118
    public function lineTo($x, $y, $absolute = true)
119
    {
120
        $this->buildPath($absolute ? 'L' : 'l', $x, $y);
121
122
        return $this;
123
    }
124
125
    /**
126
     * Draws a horizontal line from the current point (cpx, cpy) to (x, cpy). H (uppercase) indicates that absolute
127
     * coordinates will follow; h (lowercase) indicates that relative coordinates will follow. Multiple x values can be
128
     * provided (although usually this doesn't make sense). At the end of the command, the new current point becomes
129
     * (x, cpy) for the final value of x.
130
     *
131
     * @link https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
132
     *
133
     * @param float $x The absolute (relative) X coordinate for the end point of this path segment.
134
     *
135
     * @param bool  $absolute
136
     *
137
     * @return $this
138
     */
139
    public function hLineTo($x, $absolute = true)
140
    {
141
        $this->buildPath($absolute ? 'H' : 'h', $x);
142
143
        return $this;
144
    }
145
146
    /**
147
     * Draws a vertical line from the current point (cpx, cpy) to (cpx, y). V (uppercase) indicates that absolute
148
     * coordinates will follow; v (lowercase) indicates that relative coordinates will follow. Multiple y values can be
149
     * provided (although usually this doesn't make sense). At the end of the command, the new current point becomes
150
     * (cpx, y) for the final value of y.
151
     *
152
     * @link https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
153
     *
154
     * @param float $y The absolute (relative) Y coordinate for the end point of this path segment.
155
     *
156
     * @param bool  $absolute
157
     *
158
     * @return $this
159
     */
160
    public function vLineTo($y, $absolute = true)
161
    {
162
        $this->buildPath($absolute ? 'V' : 'v', $y);
163
164
        return $this;
165
    }
166
167
    /**
168
     * Draws a cubic Bézier curve from the current point to (x,y) using (x1,y1) as the control point at the beginning
169
     * of the curve and (x2,y2) as the control point at the end of the curve.
170
     *
171
     * @link https://www.w3.org/TR/SVG11/paths.html#PathDataCurveCommands
172
     *
173
     * @param float $x1 The absolute (relative) X coordinate for the first control point.
174
     * @param float $y1 The absolute (relative) Y coordinate for the first control point.
175
     * @param float $x2 The absolute (relative) X coordinate for the second control point.
176
     * @param float $y2 The absolute (relative) Y coordinate for the second control point.
177
     * @param float $x  The absolute (relative) X coordinate for the end point of this path segment.
178
     * @param float $y  The absolute (relative) Y coordinate for the end point of this path segment.
179
     *
180
     * @param bool  $absolute
181
     *
182
     * @return $this
183
     */
184
    public function curveTo($x1, $y1, $x2, $y2, $x, $y, $absolute = true)
185
    {
186
        $this->buildPath($absolute ? 'C' : 'c', $x1, $y1, $x2, $y2, $x, $y);
187
188
        return $this;
189
    }
190
191
    /**
192
     * Draws a cubic Bézier curve from the current point to (x,y). The first control point is assumed to be the
193
     * reflection of the second control point on the previous command relative to the current point. (If there is no
194
     * previous command or if the previous command was not an C, c, S or s, assume the first control point is
195
     * coincident with the current point.) (x2,y2) is the second control point (i.e., the control point at the end of
196
     * the curve). S (uppercase) indicates that absolute coordinates will follow; s (lowercase) indicates that relative
197
     * coordinates will follow. Multiple sets of coordinates may be specified to draw a polybézier. At the end of the
198
     * command, the new current point becomes the final (x,y) coordinate pair used in the polybézier.
199
     *
200
     * @link https://www.w3.org/TR/SVG11/paths.html#PathDataCurveCommands
201
     *
202
     * @param float $x2 The absolute (relative) X coordinate for the second control point.
203
     * @param float $y2 The absolute (relative) Y coordinate for the second control point.
204
     * @param float $x  The absolute (relative) X coordinate for the end point of this path segment.
205
     * @param float $y  The absolute (relative) Y coordinate for the end point of this path segment.
206
     *
207
     * @param bool  $absolute
208
     *
209
     * @return $this
210
     */
211
    public function smoothCurveTo($x2, $y2, $x, $y, $absolute = true)
212
    {
213
        $this->buildPath($absolute ? 'S' : 's', $x2, $y2, $x, $y);
214
215
        return $this;
216
    }
217
218
    /**
219
     * Draws a quadratic Bézier curve from the current point to (x,y) using (x1,y1) as the control point. Q (uppercase)
220
     * indicates that absolute coordinates will follow; q (lowercase) indicates that relative coordinates will follow.
221
     * Multiple sets of coordinates may be specified to draw a polybézier. At the end of the command, the new current
222
     * point becomes the final (x,y) coordinate pair used in the polybézier.
223
     *
224
     * @link https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands
225
     *
226
     * @param float $x1 The absolute (relative) X coordinate for the first control point.
227
     * @param float $y1 The absolute (relative) Y coordinate for the first control point.
228
     * @param float $x  The absolute (relative) X coordinate for the end point of this path segment.
229
     * @param float $y  The absolute (relative) Y coordinate for the end point of this path segment.
230
     *
231
     * @param bool  $absolute
232
     *
233
     * @return $this
234
     */
235
    public function quadraticCurveTo($x1, $y1, $x, $y, $absolute = true)
236
    {
237
        $this->buildPath($absolute ? 'Q' : 'q', $x1, $y1, $x, $y);
238
239
        return $this;
240
    }
241
242
    /**
243
     * Draws a quadratic Bézier curve from the current point to (x,y). The control point is assumed to be the
244
     * reflection of the control point on the previous command relative to the current point. (If there is no previous
245
     * command or if the previous command was not a Q, q, T or t, assume the control point is coincident with the
246
     * current point.) T (uppercase) indicates that absolute coordinates will follow; t (lowercase) indicates that
247
     * relative coordinates will follow. At the end of the command, the new current point becomes the final (x,y)
248
     * coordinate pair used in the polybézier.
249
     *
250
     * @link https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands
251
     *
252
     * @param float $x The absolute (relative) X coordinate for the end point of this path segment.
253
     * @param float $y The absolute (relative) Y coordinate for the end point of this path segment.
254
     *
255
     * @param bool  $absolute
256
     *
257
     * @return $this
258
     */
259
    public function smoothQuadraticCurveTo($x, $y, $absolute = true)
260
    {
261
        $this->buildPath($absolute ? 'T' : 't', $x, $y);
262
263
        return $this;
264
    }
265
266
    /**
267
     * Draws an elliptical arc from the current point to (x, y). The size and orientation of the ellipse are defined by
268
     * two radii (rx, ry) and an x-axis-rotation, which indicates how the ellipse as a whole is rotated relative to the
269
     * current coordinate system. The center (cx, cy) of the ellipse is calculated automatically to satisfy the
270
     * constraints imposed by the other parameters. large-arc-flag and sweep-flag contribute to the automatic
271
     * calculations and help determine how the arc is drawn.
272
     *
273
     * @link https://www.w3.org/TR/SVG11/paths.html#PathDataEllipticalArcCommands
274
     *
275
     * @param float   $rx           The x-axis radius for the ellipse (i.e., r1).
276
     * @param float   $ry           The y-axis radius for the ellipse
277
     * @param float   $xRotation    The rotation angle in degrees for the ellipse's x-axis relative to the x-axis of
278
     *                              the user coordinate system.
279
     * @param boolean $largeArcFlag The value of the large-arc-flag parameter.
280
     * @param boolean $sweepFlag    The value of the sweep-flag parameter.
281
     * @param float   $x            The absolute (relative) X coordinate for the end point of this path segment.
282
     * @param float   $y            The absolute (relative) Y coordinate for the end point of this path segment.
283
     * @param bool    $absolute
284
     *
285
     * @return $this
286
     */
287
    public function arcTo($rx, $ry, $xRotation, $largeArcFlag, $sweepFlag, $x, $y, $absolute = true)
288
    {
289
        $this->buildPath($absolute ? 'A' : 'a', [$rx, $ry], $xRotation, [$largeArcFlag ? 1 : 0, $sweepFlag ? 1 : 0], [$x, $y]);
290
291
        return $this;
292
    }
293
294
    /**
295
     * Close the current subpath by drawing a straight line from the current point to current subpath's initial point.
296
     * Since the Z and z commands take no parameters, they have an identical effect.
297
     *
298
     * @link https://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand
299
     *
300
     * @param bool $absolute
301
     */
302
    public function closePath($absolute = true)
303
    {
304
        $this->buildPath($absolute ? 'Z' : 'z');
305
    }
306
307
    public function getName()
308
    {
309
        return 'path';
310
    }
311
312
    public function getBoundingBox()
313
    {
314
        $x1 = $y1 = PHP_INT_MAX;
315
        $x2 = $y2 = -PHP_INT_MAX;
316
317
        $point = ['x' => 0, 'y' => 0];
318
319
        foreach ($this->data as $k => $value) {
320
            $pathCommand = key($value);
321
            $i = 0;
322
            while ($i < count($value)) {
323
                switch ($pathCommand) {
324
                    case 'm' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
325 View Code Duplication
                    case 'l' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
326
                        $point['x'] += $value[$i++];
327
                        $point['y'] += $value[$i++];
328
                        break;
329
                    case 'M' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
330 View Code Duplication
                    case 'L' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
331
                        $point['x'] = $value[$i++];
332
                        $point['y'] = $value[$i++];
333
                        break;
334
                    case 'v' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
335
                        $point['y'] += $value[$i++];
336
                        break;
337
                    case 'V' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
338
                        $point['y'] = $value[$i++];
339
                        break;
340
                    case 'h' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
341
                        $point['x'] += $value[$i++];
342
                        break;
343
                    case 'H' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
344
                        $point['x'] = $value[$i++];
345
                        break;
346
                    case 'Q' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
347
                        $prevData = $this->data[$k - 1];
348
                        $prevData = reset($prevData);
349
                        $p0x = end($prevData);
350
                        $p0y = prev($prevData);
351
                        list($p1x, $p1y, $p2x, $p2y) = $value['Q'];
352
353
                        $box = Bezier::quadraticBBox($p0x, $p0y, $p1x, $p1y, $p2x, $p2y);
354
355
                        $point['x'] += $box['width'] - $box['x'];
356
                        $point['y'] += $box['height'] - $box['y'];
357
358
                        $i++;
359
                        break;
360
                    case 'q':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
361
362
                    case 'z' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
363
                    case 'Z' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
364
                        break;
365
                    default :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a DEFAULT statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in the default statement.

switch ($expr) {
    default : //wrong
        doSomething();
        break;
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
366
                        //throw new \RuntimeException("Unhandled path command: " . $pathCommand);
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
367
                }
368
                $x1 = min($x1, $point['x']);
369
                $y1 = min($y1, $point['y']);
370
                $x2 = max($x2, $point['x']);
371
                $y2 = max($y2, $point['y']);
372
            }
373
        }
374
375
        return [
376
            'width' => $x2 - $x1,
377
            'height' => $y2 - $y1
378
        ];
379
    }
380
381
    protected function getCenterX()
382
    {
383
        // TODO: Implement getCenterX() method.
384
    }
385
386
    protected function getCenterY()
387
    {
388
        // TODO: Implement getCenterY() method.
389
    }
390
}