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
11 use Svg\Surface\SurfaceInterface
;
13 class Path
extends Shape
15 static $commandLengths = array(
27 static $repeatedCommands = array(
32 public function start($attributes)
34 if (!isset($attributes['d'])) {
35 $this->hasShape
= false;
41 preg_match_all('/([MZLHVCSQTAmzlhvcsqta])([eE ,\-.\d]+)*/', $attributes['d'], $commands, PREG_SET_ORDER
);
44 foreach ($commands as $c) {
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]);
52 isset(self
::$commandLengths[$commandLower]) &&
53 ($commandLength = self
::$commandLengths[$commandLower]) &&
54 count($item) > $commandLength
56 $repeatedCommand = isset(self
::$repeatedCommands[$c[1]]) ? self
::$repeatedCommands[$c[1]] : $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);
64 $command = $repeatedCommand;
67 array_unshift($item, $c[1]);
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
87 $controlX = 0; // current control point x
88 $controlY = 0; // current control point y
93 $l = 0; //-((this.width / 2) + $this.pathOffset.x),
94 $t = 0; //-((this.height / 2) + $this.pathOffset.y),
97 foreach ($path as $current) {
98 switch ($current[0]) { // first letter
99 case 'l': // lineto, relative
102 $surface->lineTo($x +
$l, $y +
$t);
105 case 'L': // lineto, absolute
108 $surface->lineTo($x +
$l, $y +
$t);
111 case 'h': // horizontal lineto, relative
113 $surface->lineTo($x +
$l, $y +
$t);
116 case 'H': // horizontal lineto, absolute
118 $surface->lineTo($x +
$l, $y +
$t);
121 case 'v': // vertical lineto, relative
123 $surface->lineTo($x +
$l, $y +
$t);
126 case 'V': // verical lineto, absolute
128 $surface->lineTo($x +
$l, $y +
$t);
131 case 'm': // moveTo, relative
136 $surface->moveTo($x +
$l, $y +
$t);
139 case 'M': // moveTo, absolute
144 $surface->moveTo($x +
$l, $y +
$t);
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
164 case 'C': // bezierCurveTo, absolute
167 $controlX = $current[3];
168 $controlY = $current[4];
169 $surface->bezierCurveTo(
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
191 // calculate reflection of previous control points
192 $controlX = 2 * $x - $controlX;
193 $controlY = 2 * $y - $controlY;
196 $surface->bezierCurveTo(
199 $x +
$current[1] +
$l,
200 $y +
$current[2] +
$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];
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
225 // calculate reflection of previous control points
226 $controlX = 2 * $x - $controlX;
227 $controlY = 2 * $y - $controlY;
230 $surface->bezierCurveTo(
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];
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(
268 case 'Q': // quadraticCurveTo, absolute
269 $tempX = $current[3];
270 $tempY = $current[4];
272 $surface->quadraticCurveTo(
280 $controlX = $current[1];
281 $controlY = $current[2];
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
296 if ($previous[0] === 't') {
297 // calculate reflection of previous control points for t
298 $controlX = 2 * $x - $tempControlX;
299 $controlY = 2 * $y - $tempControlY;
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(
320 $controlX = $x +
$current[1];
321 $controlY = $y +
$current[2];
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(
342 // TODO: optimize this
353 $current[6] +
$x +
$l,
354 $current[7] +
$y +
$t
362 // TODO: optimize this
385 $surface->closePath();
388 $previous = $current;
392 function drawArc(SurfaceInterface
$surface, $fx, $fy, $coords)
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;
433 $px = -$cosTh * $toX * 0.5 - $sinTh * $toY * 0.5;
434 $py = -$cosTh * $toY * 0.5 +
$sinTh * $toX * 0.5;
439 $pl = $rx2 * $ry2 - $rx2 * $py2 - $ry2 * $px2;
443 $s = sqrt(1 - $pl / ($rx2 * $ry2));
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) {
460 if ($sweep == 1 && $dtheta < 0) {
465 // $Convert $into $cubic $bezier $segments <= 90deg
466 $segments = ceil(abs($dtheta / M_PI
* 2));
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(
486 $fromX = $result[$i][4];
487 $fromY = $result[$i][5];
495 function segmentToBezier($th2, $th3, $cosTh, $sinTh, $rx, $ry, $cx1, $cy1, $mT, $fromX, $fromY)
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);
518 function calcVectorAngle($ux, $uy, $vx, $vy)
520 $ta = atan2($uy, $ux);
521 $tb = atan2($vy, $vx);
525 return 2 * M_PI
- ($ta - $tb);