Completed
Branch master (4af109)
by Edgar
04:43 queued 01:29
created

Path::arcTo()   A

Complexity

Conditions 4
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.2
cc 4
eloc 3
nc 1
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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->checkFirstModifier();
63
        $modifier = $absolute ? 'M' : 'm';
64
        $this->d = "$modifier $x, $y";
65
66
        return $this;
67
    }
68
69
    /**
70
     * @param string $type
71
     */
72
    private function buildPath($type)
73
    {
74
        $params = array_slice(func_get_args(), 1);
75
        $this->addData($type, $params);
76
77
        $this->d .= " $type";
78
        foreach ($params as $key => $value) {
79
            if (is_array($value)) {
80
                $this->addArrayToPath($value);
81
            } else {
82
                if ($key % 2 !== 0 && !is_array($params[$key - 1])) {
83
                    $this->d .= ", $value";
84
                } else {
85
                    $this->d .= " $value";
86
                }
87
            }
88
        }
89
    }
90
91
    private function addData($type, array $params)
92
    {
93
        $this->data[] = [$type => $params];
94
    }
95
96
    /**
97
     * Draw a line from the current point to the given (x,y) coordinate which becomes the new current point. L
98
     * (uppercase) indicates that absolute coordinates will follow; l (lowercase) indicates that relative coordinates
99
     * will follow. A number of coordinates pairs may be specified to draw a polyline. At the end of the command, the
100
     * new current point is set to the final set of coordinates provided.
101
     *
102
     * @link https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
103
     *
104
     * @param float $x The absolute (relative) X coordinate for the end point of this path segment.
105
     * @param float $y The absolute (relative) Y coordinate for the end point of this path segment.
106
     * @param bool  $absolute
107
     *
108
     * @return $this
109
     */
110
    public function lineTo($x, $y, $absolute = true)
111
    {
112
        $this->buildPath($absolute ? 'L' : 'l', $x, $y);
113
114
        return $this;
115
    }
116
117
    /**
118
     * Draws a horizontal line from the current point (cpx, cpy) to (x, cpy). H (uppercase) indicates that absolute
119
     * coordinates will follow; h (lowercase) indicates that relative coordinates will follow. Multiple x values can be
120
     * provided (although usually this doesn't make sense). At the end of the command, the new current point becomes
121
     * (x, cpy) for the final value of x.
122
     *
123
     * @link https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
124
     *
125
     * @param float $x The absolute (relative) X coordinate for the end point of this path segment.
126
     *
127
     * @param bool  $absolute
128
     *
129
     * @return $this
130
     */
131
    public function hLineTo($x, $absolute = true)
132
    {
133
        $this->buildPath($absolute ? 'H' : 'h', $x);
134
135
        return $this;
136
    }
137
138
    /**
139
     * Draws a vertical line from the current point (cpx, cpy) to (cpx, y). V (uppercase) indicates that absolute
140
     * coordinates will follow; v (lowercase) indicates that relative coordinates will follow. Multiple y values can be
141
     * provided (although usually this doesn't make sense). At the end of the command, the new current point becomes
142
     * (cpx, y) for the final value of y.
143
     *
144
     * @link https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
145
     *
146
     * @param float $y The absolute (relative) Y coordinate for the end point of this path segment.
147
     *
148
     * @param bool  $absolute
149
     *
150
     * @return $this
151
     */
152
    public function vLineTo($y, $absolute = true)
153
    {
154
        $this->buildPath($absolute ? 'V' : 'v', $y);
155
156
        return $this;
157
    }
158
159
    /**
160
     * Draws a cubic Bézier curve from the current point to (x,y) using (x1,y1) as the control point at the beginning
161
     * of the curve and (x2,y2) as the control point at the end of the curve.
162
     *
163
     * @link https://www.w3.org/TR/SVG11/paths.html#PathDataCurveCommands
164
     *
165
     * @param float $x1 The absolute (relative) X coordinate for the first control point.
166
     * @param float $y1 The absolute (relative) Y coordinate for the first control point.
167
     * @param float $x2 The absolute (relative) X coordinate for the second control point.
168
     * @param float $y2 The absolute (relative) Y coordinate for the second control point.
169
     * @param float $x  The absolute (relative) X coordinate for the end point of this path segment.
170
     * @param float $y  The absolute (relative) Y coordinate for the end point of this path segment.
171
     *
172
     * @param bool  $absolute
173
     *
174
     * @return $this
175
     */
176
    public function curveTo($x1, $y1, $x2, $y2, $x, $y, $absolute = true)
177
    {
178
        $this->buildPath($absolute ? 'C' : 'c', $x1, $y1, $x2, $y2, $x, $y);
179
180
        return $this;
181
    }
182
183
    /**
184
     * Draws a cubic Bézier curve from the current point to (x,y). The first control point is assumed to be the
185
     * reflection of the second control point on the previous command relative to the current point. (If there is no
186
     * previous command or if the previous command was not an C, c, S or s, assume the first control point is
187
     * coincident with the current point.) (x2,y2) is the second control point (i.e., the control point at the end of
188
     * the curve). S (uppercase) indicates that absolute coordinates will follow; s (lowercase) indicates that relative
189
     * coordinates will follow. Multiple sets of coordinates may be specified to draw a polybézier. At the end of the
190
     * command, the new current point becomes the final (x,y) coordinate pair used in the polybézier.
191
     *
192
     * @link https://www.w3.org/TR/SVG11/paths.html#PathDataCurveCommands
193
     *
194
     * @param float $x2 The absolute (relative) X coordinate for the second control point.
195
     * @param float $y2 The absolute (relative) Y coordinate for the second control point.
196
     * @param float $x  The absolute (relative) X coordinate for the end point of this path segment.
197
     * @param float $y  The absolute (relative) Y coordinate for the end point of this path segment.
198
     *
199
     * @param bool  $absolute
200
     *
201
     * @return $this
202
     */
203
    public function smoothCurveTo($x2, $y2, $x, $y, $absolute = true)
204
    {
205
        $this->buildPath($absolute ? 'S' : 's', $x2, $y2, $x, $y);
206
207
        return $this;
208
    }
209
210
    /**
211
     * Draws a quadratic Bézier curve from the current point to (x,y) using (x1,y1) as the control point. Q (uppercase)
212
     * indicates that absolute coordinates will follow; q (lowercase) indicates that relative coordinates will follow.
213
     * Multiple sets of coordinates may be specified to draw a polybézier. At the end of the command, the new current
214
     * point becomes the final (x,y) coordinate pair used in the polybézier.
215
     *
216
     * @link https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands
217
     *
218
     * @param float $x1 The absolute (relative) X coordinate for the first control point.
219
     * @param float $y1 The absolute (relative) Y coordinate for the first control point.
220
     * @param float $x  The absolute (relative) X coordinate for the end point of this path segment.
221
     * @param float $y  The absolute (relative) Y coordinate for the end point of this path segment.
222
     *
223
     * @param bool  $absolute
224
     *
225
     * @return $this
226
     */
227
    public function quadraticCurveTo($x1, $y1, $x, $y, $absolute = true)
228
    {
229
        $this->buildPath($absolute ? 'Q' : 'q', $x1, $y1, $x, $y);
230
231
        return $this;
232
    }
233
234
    /**
235
     * Draws a quadratic Bézier curve from the current point to (x,y). The control point is assumed to be the
236
     * reflection of the control point on the previous command relative to the current point. (If there is no previous
237
     * command or if the previous command was not a Q, q, T or t, assume the control point is coincident with the
238
     * current point.) T (uppercase) indicates that absolute coordinates will follow; t (lowercase) indicates that
239
     * relative coordinates will follow. At the end of the command, the new current point becomes the final (x,y)
240
     * coordinate pair used in the polybézier.
241
     *
242
     * @link https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands
243
     *
244
     * @param float $x The absolute (relative) X coordinate for the end point of this path segment.
245
     * @param float $y The absolute (relative) Y coordinate for the end point of this path segment.
246
     *
247
     * @param bool  $absolute
248
     *
249
     * @return $this
250
     */
251
    public function smoothQuadraticCurveTo($x, $y, $absolute = true)
252
    {
253
        $this->buildPath($absolute ? 'T' : 't', $x, $y);
254
255
        return $this;
256
    }
257
258
    /**
259
     * Draws an elliptical arc from the current point to (x, y). The size and orientation of the ellipse are defined by
260
     * two radii (rx, ry) and an x-axis-rotation, which indicates how the ellipse as a whole is rotated relative to the
261
     * current coordinate system. The center (cx, cy) of the ellipse is calculated automatically to satisfy the
262
     * constraints imposed by the other parameters. large-arc-flag and sweep-flag contribute to the automatic
263
     * calculations and help determine how the arc is drawn.
264
     *
265
     * @link https://www.w3.org/TR/SVG11/paths.html#PathDataEllipticalArcCommands
266
     *
267
     * @param float   $rx           The x-axis radius for the ellipse (i.e., r1).
268
     * @param float   $ry           The y-axis radius for the ellipse
269
     * @param float   $xRotation    The rotation angle in degrees for the ellipse's x-axis relative to the x-axis of
270
     *                              the user coordinate system.
271
     * @param boolean $largeArcFlag The value of the large-arc-flag parameter.
272
     * @param boolean $sweepFlag    The value of the sweep-flag parameter.
273
     * @param float   $x            The absolute (relative) X coordinate for the end point of this path segment.
274
     * @param float   $y            The absolute (relative) Y coordinate for the end point of this path segment.
275
     * @param bool    $absolute
276
     *
277
     * @return $this
278
     */
279
    public function arcTo($rx, $ry, $xRotation, $largeArcFlag, $sweepFlag, $x, $y, $absolute = true)
280
    {
281
        $this->buildPath($absolute ? 'A' : 'a', [$rx, $ry], $xRotation, [$largeArcFlag ? 1 : 0, $sweepFlag ? 1 : 0], [$x, $y]);
282
283
        return $this;
284
    }
285
286
    /**
287
     * Close the current subpath by drawing a straight line from the current point to current subpath's initial point.
288
     * Since the Z and z commands take no parameters, they have an identical effect.
289
     *
290
     * @link https://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand
291
     *
292
     * @param bool $absolute
293
     */
294
    public function closePath($absolute = true)
295
    {
296
        $this->buildPath($absolute ? 'Z' : 'z');
297
    }
298
299
    public function getName()
300
    {
301
        return 'path';
302
    }
303
304
    public function getBoundingBox()
305
    {
306
        $x1 = $y1 = PHP_INT_MAX;
307
        $x2 = $y2 = -PHP_INT_MAX;
308
309
        $point = ['x' => 0, 'y' => 0];
310
311
        foreach ($this->data as $k => $value) {
312
            $pathCommand = key($value);
313
            $i = 0;
314
            while ($i < count($value)) {
315
                switch ($pathCommand) {
316
                    case 'm':
317 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...
318
                        $point['x'] += $value[$i++];
319
                        $point['y'] += $value[$i++];
320
                        break;
321
                    case 'M':
322 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...
323
                        $point['x'] = $value[$i++];
324
                        $point['y'] = $value[$i++];
325
                        break;
326
                    case 'v':
327
                        $point['y'] += $value[$i++];
328
                        break;
329
                    case 'V':
330
                        $point['y'] = $value[$i++];
331
                        break;
332
                    case 'h':
333
                        $point['x'] += $value[$i++];
334
                        break;
335
                    case 'H':
336
                        $point['x'] = $value[$i++];
337
                        break;
338
                    case 'Q':
339
                        $prevData = $this->data[$k - 1];
340
                        $prevData = reset($prevData);
341
                        $p0x = end($prevData);
342
                        $p0y = prev($prevData);
343
                        list($p1x, $p1y, $p2x, $p2y) = $value['Q'];
344
345
                        $box = Bezier::quadraticBBox($p0x, $p0y, $p1x, $p1y, $p2x, $p2y);
346
347
                        $point['x'] += $box['width'] - $box['x'];
348
                        $point['y'] += $box['height'] - $box['y'];
349
350
                        $i++;
351
                        break;
352
                    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...
353
354
                    case 'z':
355
                    case 'Z':
356
                        break;
357
                    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...
358
                        //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...
359
                }
360
                $x1 = min($x1, $point['x']);
361
                $y1 = min($y1, $point['y']);
362
                $x2 = max($x2, $point['x']);
363
                $y2 = max($y2, $point['y']);
364
            }
365
        }
366
367
        return [
368
            'width' => $x2 - $x1,
369
            'height' => $y2 - $y1
370
        ];
371
    }
372
373
    protected function getCenterX()
374
    {
375
        // TODO: Implement getCenterX() method.
376
    }
377
378
    protected function getCenterY()
379
    {
380
        // TODO: Implement getCenterY() method.
381
    }
382
383
    /**
384
     * @param $value
385
     */
386
    private function addArrayToPath(array $value)
387
    {
388
        foreach ($value as $item) {
389
            $this->d .= " $item,";
390
        }
391
        $this->d = rtrim($this->d, ',');
392
    }
393
394
    /**
395
     *
396
     */
397
    private function checkFirstModifier()
398
    {
399
        if ($this->d !== null) {
400
            throw new \InvalidArgumentException("First modifier for path must be: M");
401
        }
402
    }
403
}