composer package updates
[openemr.git] / vendor / phenx / php-svg-lib / src / Svg / Tag / Path.php
blobc43d6388cbffafaf9c007a27b7bc0f7c201ee615
1 <?php
2 /**
3 * @package php-svg-lib
4 * @link http://github.com/PhenX/php-svg-lib
5 * @author Fabien Ménager <fabien.menager@gmail.com>
6 * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
7 */
9 namespace Svg\Tag;
11 use Svg\Surface\SurfaceInterface;
13 class Path extends Shape
15 static $commandLengths = array(
16 'm' => 2,
17 'l' => 2,
18 'h' => 1,
19 'v' => 1,
20 'c' => 6,
21 's' => 4,
22 'q' => 4,
23 't' => 2,
24 'a' => 7,
27 static $repeatedCommands = array(
28 'm' => 'l',
29 'M' => 'L',
32 public function start($attributes)
34 if (!isset($attributes['d'])) {
35 $this->hasShape = false;
37 return;
40 $commands = array();
41 preg_match_all('/([MZLHVCSQTAmzlhvcsqta])([eE ,\-.\d]+)*/', $attributes['d'], $commands, PREG_SET_ORDER);
43 $path = array();
44 foreach ($commands as $c) {
45 if (count($c) == 3) {
46 $arguments = array();
47 preg_match_all('/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/i', $c[2], $arguments, PREG_PATTERN_ORDER);
48 $item = $arguments[0];
49 $commandLower = strtolower($c[1]);
51 if (
52 isset(self::$commandLengths[$commandLower]) &&
53 ($commandLength = self::$commandLengths[$commandLower]) &&
54 count($item) > $commandLength
55 ) {
56 $repeatedCommand = isset(self::$repeatedCommands[$c[1]]) ? self::$repeatedCommands[$c[1]] : $c[1];
57 $command = $c[1];
59 for ($k = 0, $klen = count($item); $k < $klen; $k += $commandLength) {
60 $_item = array_slice($item, $k, $k + $commandLength);
61 array_unshift($_item, $command);
62 $path[] = $_item;
64 $command = $repeatedCommand;
66 } else {
67 array_unshift($item, $c[1]);
68 $path[] = $item;
71 } else {
72 $item = array($c[1]);
74 $path[] = $item;
78 $surface = $this->document->getSurface();
80 // From https://github.com/kangax/fabric.js/blob/master/src/shapes/path.class.js
81 $current = null; // current instruction
82 $previous = null;
83 $subpathStartX = 0;
84 $subpathStartY = 0;
85 $x = 0; // current x
86 $y = 0; // current y
87 $controlX = 0; // current control point x
88 $controlY = 0; // current control point y
89 $tempX = null;
90 $tempY = null;
91 $tempControlX = null;
92 $tempControlY = null;
93 $l = 0; //-((this.width / 2) + $this.pathOffset.x),
94 $t = 0; //-((this.height / 2) + $this.pathOffset.y),
95 $methodName = null;
97 foreach ($path as $current) {
98 switch ($current[0]) { // first letter
99 case 'l': // lineto, relative
100 $x += $current[1];
101 $y += $current[2];
102 $surface->lineTo($x + $l, $y + $t);
103 break;
105 case 'L': // lineto, absolute
106 $x = $current[1];
107 $y = $current[2];
108 $surface->lineTo($x + $l, $y + $t);
109 break;
111 case 'h': // horizontal lineto, relative
112 $x += $current[1];
113 $surface->lineTo($x + $l, $y + $t);
114 break;
116 case 'H': // horizontal lineto, absolute
117 $x = $current[1];
118 $surface->lineTo($x + $l, $y + $t);
119 break;
121 case 'v': // vertical lineto, relative
122 $y += $current[1];
123 $surface->lineTo($x + $l, $y + $t);
124 break;
126 case 'V': // verical lineto, absolute
127 $y = $current[1];
128 $surface->lineTo($x + $l, $y + $t);
129 break;
131 case 'm': // moveTo, relative
132 $x += $current[1];
133 $y += $current[2];
134 $subpathStartX = $x;
135 $subpathStartY = $y;
136 $surface->moveTo($x + $l, $y + $t);
137 break;
139 case 'M': // moveTo, absolute
140 $x = $current[1];
141 $y = $current[2];
142 $subpathStartX = $x;
143 $subpathStartY = $y;
144 $surface->moveTo($x + $l, $y + $t);
145 break;
147 case 'c': // bezierCurveTo, relative
148 $tempX = $x + $current[5];
149 $tempY = $y + $current[6];
150 $controlX = $x + $current[3];
151 $controlY = $y + $current[4];
152 $surface->bezierCurveTo(
153 $x + $current[1] + $l, // x1
154 $y + $current[2] + $t, // y1
155 $controlX + $l, // x2
156 $controlY + $t, // y2
157 $tempX + $l,
158 $tempY + $t
160 $x = $tempX;
161 $y = $tempY;
162 break;
164 case 'C': // bezierCurveTo, absolute
165 $x = $current[5];
166 $y = $current[6];
167 $controlX = $current[3];
168 $controlY = $current[4];
169 $surface->bezierCurveTo(
170 $current[1] + $l,
171 $current[2] + $t,
172 $controlX + $l,
173 $controlY + $t,
174 $x + $l,
175 $y + $t
177 break;
179 case 's': // shorthand cubic bezierCurveTo, relative
181 // transform to absolute x,y
182 $tempX = $x + $current[3];
183 $tempY = $y + $current[4];
185 if (!preg_match('/[CcSs]/', $previous[0])) {
186 // If there is no previous command or if the previous command was not a C, c, S, or s,
187 // the control point is coincident with the current point
188 $controlX = $x;
189 $controlY = $y;
190 } else {
191 // calculate reflection of previous control points
192 $controlX = 2 * $x - $controlX;
193 $controlY = 2 * $y - $controlY;
196 $surface->bezierCurveTo(
197 $controlX + $l,
198 $controlY + $t,
199 $x + $current[1] + $l,
200 $y + $current[2] + $t,
201 $tempX + $l,
202 $tempY + $t
204 // set control point to 2nd one of this command
205 // "... the first control point is assumed to be
206 // the reflection of the second control point on
207 // the previous command relative to the current point."
208 $controlX = $x + $current[1];
209 $controlY = $y + $current[2];
211 $x = $tempX;
212 $y = $tempY;
213 break;
215 case 'S': // shorthand cubic bezierCurveTo, absolute
216 $tempX = $current[3];
217 $tempY = $current[4];
219 if (!preg_match('/[CcSs]/', $previous[0])) {
220 // If there is no previous command or if the previous command was not a C, c, S, or s,
221 // the control point is coincident with the current point
222 $controlX = $x;
223 $controlY = $y;
224 } else {
225 // calculate reflection of previous control points
226 $controlX = 2 * $x - $controlX;
227 $controlY = 2 * $y - $controlY;
230 $surface->bezierCurveTo(
231 $controlX + $l,
232 $controlY + $t,
233 $current[1] + $l,
234 $current[2] + $t,
235 $tempX + $l,
236 $tempY + $t
238 $x = $tempX;
239 $y = $tempY;
241 // set control point to 2nd one of this command
242 // "... the first control point is assumed to be
243 // the reflection of the second control point on
244 // the previous command relative to the current point."
245 $controlX = $current[1];
246 $controlY = $current[2];
248 break;
250 case 'q': // quadraticCurveTo, relative
251 // transform to absolute x,y
252 $tempX = $x + $current[3];
253 $tempY = $y + $current[4];
255 $controlX = $x + $current[1];
256 $controlY = $y + $current[2];
258 $surface->quadraticCurveTo(
259 $controlX + $l,
260 $controlY + $t,
261 $tempX + $l,
262 $tempY + $t
264 $x = $tempX;
265 $y = $tempY;
266 break;
268 case 'Q': // quadraticCurveTo, absolute
269 $tempX = $current[3];
270 $tempY = $current[4];
272 $surface->quadraticCurveTo(
273 $current[1] + $l,
274 $current[2] + $t,
275 $tempX + $l,
276 $tempY + $t
278 $x = $tempX;
279 $y = $tempY;
280 $controlX = $current[1];
281 $controlY = $current[2];
282 break;
284 case 't': // shorthand quadraticCurveTo, relative
286 // transform to absolute x,y
287 $tempX = $x + $current[1];
288 $tempY = $y + $current[2];
290 if (preg_match("/[QqTt]/", $previous[0])) {
291 // If there is no previous command or if the previous command was not a Q, q, T or t,
292 // assume the control point is coincident with the current point
293 $controlX = $x;
294 $controlY = $y;
295 } else {
296 if ($previous[0] === 't') {
297 // calculate reflection of previous control points for t
298 $controlX = 2 * $x - $tempControlX;
299 $controlY = 2 * $y - $tempControlY;
300 } else {
301 if ($previous[0] === 'q') {
302 // calculate reflection of previous control points for q
303 $controlX = 2 * $x - $controlX;
304 $controlY = 2 * $y - $controlY;
309 $tempControlX = $controlX;
310 $tempControlY = $controlY;
312 $surface->quadraticCurveTo(
313 $controlX + $l,
314 $controlY + $t,
315 $tempX + $l,
316 $tempY + $t
318 $x = $tempX;
319 $y = $tempY;
320 $controlX = $x + $current[1];
321 $controlY = $y + $current[2];
322 break;
324 case 'T':
325 $tempX = $current[1];
326 $tempY = $current[2];
328 // calculate reflection of previous control points
329 $controlX = 2 * $x - $controlX;
330 $controlY = 2 * $y - $controlY;
331 $surface->quadraticCurveTo(
332 $controlX + $l,
333 $controlY + $t,
334 $tempX + $l,
335 $tempY + $t
337 $x = $tempX;
338 $y = $tempY;
339 break;
341 case 'a':
342 // TODO: optimize this
343 $this->drawArc(
344 $surface,
345 $x + $l,
346 $y + $t,
347 array(
348 $current[1],
349 $current[2],
350 $current[3],
351 $current[4],
352 $current[5],
353 $current[6] + $x + $l,
354 $current[7] + $y + $t
357 $x += $current[6];
358 $y += $current[7];
359 break;
361 case 'A':
362 // TODO: optimize this
363 $this->drawArc(
364 $surface,
365 $x + $l,
366 $y + $t,
367 array(
368 $current[1],
369 $current[2],
370 $current[3],
371 $current[4],
372 $current[5],
373 $current[6] + $l,
374 $current[7] + $t
377 $x = $current[6];
378 $y = $current[7];
379 break;
381 case 'z':
382 case 'Z':
383 $x = $subpathStartX;
384 $y = $subpathStartY;
385 $surface->closePath();
386 break;
388 $previous = $current;
392 function drawArc(SurfaceInterface $surface, $fx, $fy, $coords)
394 $rx = $coords[0];
395 $ry = $coords[1];
396 $rot = $coords[2];
397 $large = $coords[3];
398 $sweep = $coords[4];
399 $tx = $coords[5];
400 $ty = $coords[6];
401 $segs = array(
402 array(),
403 array(),
404 array(),
405 array(),
408 $segsNorm = $this->arcToSegments($tx - $fx, $ty - $fy, $rx, $ry, $large, $sweep, $rot);
410 for ($i = 0, $len = count($segsNorm); $i < $len; $i++) {
411 $segs[$i][0] = $segsNorm[$i][0] + $fx;
412 $segs[$i][1] = $segsNorm[$i][1] + $fy;
413 $segs[$i][2] = $segsNorm[$i][2] + $fx;
414 $segs[$i][3] = $segsNorm[$i][3] + $fy;
415 $segs[$i][4] = $segsNorm[$i][4] + $fx;
416 $segs[$i][5] = $segsNorm[$i][5] + $fy;
418 call_user_func_array(array($surface, "bezierCurveTo"), $segs[$i]);
422 function arcToSegments($toX, $toY, $rx, $ry, $large, $sweep, $rotateX)
424 $th = $rotateX * M_PI / 180;
425 $sinTh = sin($th);
426 $cosTh = cos($th);
427 $fromX = 0;
428 $fromY = 0;
430 $rx = abs($rx);
431 $ry = abs($ry);
433 $px = -$cosTh * $toX * 0.5 - $sinTh * $toY * 0.5;
434 $py = -$cosTh * $toY * 0.5 + $sinTh * $toX * 0.5;
435 $rx2 = $rx * $rx;
436 $ry2 = $ry * $ry;
437 $py2 = $py * $py;
438 $px2 = $px * $px;
439 $pl = $rx2 * $ry2 - $rx2 * $py2 - $ry2 * $px2;
440 $root = 0;
442 if ($pl < 0) {
443 $s = sqrt(1 - $pl / ($rx2 * $ry2));
444 $rx *= $s;
445 $ry *= $s;
446 } else {
447 $root = ($large == $sweep ? -1.0 : 1.0) * sqrt($pl / ($rx2 * $py2 + $ry2 * $px2));
450 $cx = $root * $rx * $py / $ry;
451 $cy = -$root * $ry * $px / $rx;
452 $cx1 = $cosTh * $cx - $sinTh * $cy + $toX * 0.5;
453 $cy1 = $sinTh * $cx + $cosTh * $cy + $toY * 0.5;
454 $mTheta = $this->calcVectorAngle(1, 0, ($px - $cx) / $rx, ($py - $cy) / $ry);
455 $dtheta = $this->calcVectorAngle(($px - $cx) / $rx, ($py - $cy) / $ry, (-$px - $cx) / $rx, (-$py - $cy) / $ry);
457 if ($sweep == 0 && $dtheta > 0) {
458 $dtheta -= 2 * M_PI;
459 } else {
460 if ($sweep == 1 && $dtheta < 0) {
461 $dtheta += 2 * M_PI;
465 // $Convert $into $cubic $bezier $segments <= 90deg
466 $segments = ceil(abs($dtheta / M_PI * 2));
467 $result = array();
468 $mDelta = $dtheta / $segments;
469 $mT = 8 / 3 * sin($mDelta / 4) * sin($mDelta / 4) / sin($mDelta / 2);
470 $th3 = $mTheta + $mDelta;
472 for ($i = 0; $i < $segments; $i++) {
473 $result[$i] = $this->segmentToBezier(
474 $mTheta,
475 $th3,
476 $cosTh,
477 $sinTh,
478 $rx,
479 $ry,
480 $cx1,
481 $cy1,
482 $mT,
483 $fromX,
484 $fromY
486 $fromX = $result[$i][4];
487 $fromY = $result[$i][5];
488 $mTheta = $th3;
489 $th3 += $mDelta;
492 return $result;
495 function segmentToBezier($th2, $th3, $cosTh, $sinTh, $rx, $ry, $cx1, $cy1, $mT, $fromX, $fromY)
497 $costh2 = cos($th2);
498 $sinth2 = sin($th2);
499 $costh3 = cos($th3);
500 $sinth3 = sin($th3);
501 $toX = $cosTh * $rx * $costh3 - $sinTh * $ry * $sinth3 + $cx1;
502 $toY = $sinTh * $rx * $costh3 + $cosTh * $ry * $sinth3 + $cy1;
503 $cp1X = $fromX + $mT * (-$cosTh * $rx * $sinth2 - $sinTh * $ry * $costh2);
504 $cp1Y = $fromY + $mT * (-$sinTh * $rx * $sinth2 + $cosTh * $ry * $costh2);
505 $cp2X = $toX + $mT * ($cosTh * $rx * $sinth3 + $sinTh * $ry * $costh3);
506 $cp2Y = $toY + $mT * ($sinTh * $rx * $sinth3 - $cosTh * $ry * $costh3);
508 return array(
509 $cp1X,
510 $cp1Y,
511 $cp2X,
512 $cp2Y,
513 $toX,
514 $toY
518 function calcVectorAngle($ux, $uy, $vx, $vy)
520 $ta = atan2($uy, $ux);
521 $tb = atan2($vy, $vx);
522 if ($tb >= $ta) {
523 return $tb - $ta;
524 } else {
525 return 2 * M_PI - ($ta - $tb);