4 * @link http://github.com/PhenX/php-svg-lib
5 * @author Fabien Ménager <fabien.menager@gmail.com>
6 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
11 use Svg\Surface\SurfaceInterface
;
13 class Path
extends Shape
15 static $commandLengths = array(
27 static $repeatedCommands = array(
32 public function start($attribs)
34 if (!isset($attribs['d'])) {
35 $this->hasShape
= false;
41 preg_match_all('/([MZLHVCSQTAmzlhvcsqta])([eE ,\-.\d]+)*/', $attribs['d'], $commands, PREG_SET_ORDER
);
44 foreach ($commands as $c) {
47 preg_match_all('/[\-^]?[\d.]+(e[\-]?[\d]+){0,1}/i', $c[2], $arguments, PREG_PATTERN_ORDER
);
49 $item = $arguments[0];
50 $commandLower = strtolower($c[1]);
53 isset(self
::$commandLengths[$commandLower]) &&
54 ($commandLength = self
::$commandLengths[$commandLower]) &&
55 count($item) > $commandLength
57 $repeatedCommand = isset(self
::$repeatedCommands[$c[1]]) ? self
::$repeatedCommands[$c[1]] : $c[1];
60 for ($k = 0, $klen = count($item); $k < $klen; $k +
= $commandLength) {
61 $_item = array_slice($item, $k, $k +
$commandLength);
62 array_unshift($_item, $command);
65 $command = $repeatedCommand;
68 array_unshift($item, $c[1]);
79 $surface = $this->document
->getSurface();
81 // From https://github.com/kangax/fabric.js/blob/master/src/shapes/path.class.js
82 $current = null; // current instruction
88 $controlX = 0; // current control point x
89 $controlY = 0; // current control point y
94 $l = 0; //-((this.width / 2) + $this.pathOffset.x),
95 $t = 0; //-((this.height / 2) + $this.pathOffset.y),
98 foreach ($path as $current) {
99 switch ($current[0]) { // first letter
100 case 'l': // lineto, relative
103 $surface->lineTo($x +
$l, $y +
$t);
106 case 'L': // lineto, absolute
109 $surface->lineTo($x +
$l, $y +
$t);
112 case 'h': // horizontal lineto, relative
114 $surface->lineTo($x +
$l, $y +
$t);
117 case 'H': // horizontal lineto, absolute
119 $surface->lineTo($x +
$l, $y +
$t);
122 case 'v': // vertical lineto, relative
124 $surface->lineTo($x +
$l, $y +
$t);
127 case 'V': // verical lineto, absolute
129 $surface->lineTo($x +
$l, $y +
$t);
132 case 'm': // moveTo, relative
137 $surface->moveTo($x +
$l, $y +
$t);
140 case 'M': // moveTo, absolute
145 $surface->moveTo($x +
$l, $y +
$t);
148 case 'c': // bezierCurveTo, relative
149 $tempX = $x +
$current[5];
150 $tempY = $y +
$current[6];
151 $controlX = $x +
$current[3];
152 $controlY = $y +
$current[4];
153 $surface->bezierCurveTo(
154 $x +
$current[1] +
$l, // x1
155 $y +
$current[2] +
$t, // y1
156 $controlX +
$l, // x2
157 $controlY +
$t, // y2
165 case 'C': // bezierCurveTo, absolute
168 $controlX = $current[3];
169 $controlY = $current[4];
170 $surface->bezierCurveTo(
180 case 's': // shorthand cubic bezierCurveTo, relative
182 // transform to absolute x,y
183 $tempX = $x +
$current[3];
184 $tempY = $y +
$current[4];
186 if (!preg_match('/[CcSs]/', $previous[0])) {
187 // If there is no previous command or if the previous command was not a C, c, S, or s,
188 // the control point is coincident with the current point
192 // calculate reflection of previous control points
193 $controlX = 2 * $x - $controlX;
194 $controlY = 2 * $y - $controlY;
197 $surface->bezierCurveTo(
200 $x +
$current[1] +
$l,
201 $y +
$current[2] +
$t,
205 // set control point to 2nd one of this command
206 // "... the first control point is assumed to be
207 // the reflection of the second control point on
208 // the previous command relative to the current point."
209 $controlX = $x +
$current[1];
210 $controlY = $y +
$current[2];
216 case 'S': // shorthand cubic bezierCurveTo, absolute
217 $tempX = $current[3];
218 $tempY = $current[4];
220 if (!preg_match('/[CcSs]/', $previous[0])) {
221 // If there is no previous command or if the previous command was not a C, c, S, or s,
222 // the control point is coincident with the current point
226 // calculate reflection of previous control points
227 $controlX = 2 * $x - $controlX;
228 $controlY = 2 * $y - $controlY;
231 $surface->bezierCurveTo(
242 // set control point to 2nd one of this command
243 // "... the first control point is assumed to be
244 // the reflection of the second control point on
245 // the previous command relative to the current point."
246 $controlX = $current[1];
247 $controlY = $current[2];
251 case 'q': // quadraticCurveTo, relative
252 // transform to absolute x,y
253 $tempX = $x +
$current[3];
254 $tempY = $y +
$current[4];
256 $controlX = $x +
$current[1];
257 $controlY = $y +
$current[2];
259 $surface->quadraticCurveTo(
269 case 'Q': // quadraticCurveTo, absolute
270 $tempX = $current[3];
271 $tempY = $current[4];
273 $surface->quadraticCurveTo(
281 $controlX = $current[1];
282 $controlY = $current[2];
285 case 't': // shorthand quadraticCurveTo, relative
287 // transform to absolute x,y
288 $tempX = $x +
$current[1];
289 $tempY = $y +
$current[2];
291 if (preg_match("/[QqTt]/", $previous[0])) {
292 // If there is no previous command or if the previous command was not a Q, q, T or t,
293 // assume the control point is coincident with the current point
297 if ($previous[0] === 't') {
298 // calculate reflection of previous control points for t
299 $controlX = 2 * $x - $tempControlX;
300 $controlY = 2 * $y - $tempControlY;
302 if ($previous[0] === 'q') {
303 // calculate reflection of previous control points for q
304 $controlX = 2 * $x - $controlX;
305 $controlY = 2 * $y - $controlY;
310 $tempControlX = $controlX;
311 $tempControlY = $controlY;
313 $surface->quadraticCurveTo(
321 $controlX = $x +
$current[1];
322 $controlY = $y +
$current[2];
326 $tempX = $current[1];
327 $tempY = $current[2];
329 // calculate reflection of previous control points
330 $controlX = 2 * $x - $controlX;
331 $controlY = 2 * $y - $controlY;
332 $surface->quadraticCurveTo(
343 // TODO: optimize this
354 $current[6] +
$x +
$l,
355 $current[7] +
$y +
$t
363 // TODO: optimize this
386 $surface->closePath();
389 $previous = $current;
393 function drawArc(SurfaceInterface
$surface, $fx, $fy, $coords)
409 $segsNorm = $this->arcToSegments($tx - $fx, $ty - $fy, $rx, $ry, $large, $sweep, $rot);
411 for ($i = 0, $len = count($segsNorm); $i < $len; $i++
) {
412 $segs[$i][0] = $segsNorm[$i][0] +
$fx;
413 $segs[$i][1] = $segsNorm[$i][1] +
$fy;
414 $segs[$i][2] = $segsNorm[$i][2] +
$fx;
415 $segs[$i][3] = $segsNorm[$i][3] +
$fy;
416 $segs[$i][4] = $segsNorm[$i][4] +
$fx;
417 $segs[$i][5] = $segsNorm[$i][5] +
$fy;
419 call_user_func_array(array($surface, "bezierCurveTo"), $segs[$i]);
423 function arcToSegments($toX, $toY, $rx, $ry, $large, $sweep, $rotateX)
425 $th = $rotateX * M_PI
/ 180;
434 $px = -$cosTh * $toX * 0.5 - $sinTh * $toY * 0.5;
435 $py = -$cosTh * $toY * 0.5 +
$sinTh * $toX * 0.5;
440 $pl = $rx2 * $ry2 - $rx2 * $py2 - $ry2 * $px2;
444 $s = sqrt(1 - $pl / ($rx2 * $ry2));
448 $root = ($large == $sweep ?
-1.0 : 1.0) * sqrt($pl / ($rx2 * $py2 +
$ry2 * $px2));
451 $cx = $root * $rx * $py / $ry;
452 $cy = -$root * $ry * $px / $rx;
453 $cx1 = $cosTh * $cx - $sinTh * $cy +
$toX * 0.5;
454 $cy1 = $sinTh * $cx +
$cosTh * $cy +
$toY * 0.5;
455 $mTheta = $this->calcVectorAngle(1, 0, ($px - $cx) / $rx, ($py - $cy) / $ry);
456 $dtheta = $this->calcVectorAngle(($px - $cx) / $rx, ($py - $cy) / $ry, (-$px - $cx) / $rx, (-$py - $cy) / $ry);
458 if ($sweep == 0 && $dtheta > 0) {
461 if ($sweep == 1 && $dtheta < 0) {
466 // $Convert $into $cubic $bezier $segments <= 90deg
467 $segments = ceil(abs($dtheta / M_PI
* 2));
469 $mDelta = $dtheta / $segments;
470 $mT = 8 / 3 * sin($mDelta / 4) * sin($mDelta / 4) / sin($mDelta / 2);
471 $th3 = $mTheta +
$mDelta;
473 for ($i = 0; $i < $segments; $i++
) {
474 $result[$i] = $this->segmentToBezier(
487 $fromX = $result[$i][4];
488 $fromY = $result[$i][5];
496 function segmentToBezier($th2, $th3, $cosTh, $sinTh, $rx, $ry, $cx1, $cy1, $mT, $fromX, $fromY)
502 $toX = $cosTh * $rx * $costh3 - $sinTh * $ry * $sinth3 +
$cx1;
503 $toY = $sinTh * $rx * $costh3 +
$cosTh * $ry * $sinth3 +
$cy1;
504 $cp1X = $fromX +
$mT * (-$cosTh * $rx * $sinth2 - $sinTh * $ry * $costh2);
505 $cp1Y = $fromY +
$mT * (-$sinTh * $rx * $sinth2 +
$cosTh * $ry * $costh2);
506 $cp2X = $toX +
$mT * ($cosTh * $rx * $sinth3 +
$sinTh * $ry * $costh3);
507 $cp2Y = $toY +
$mT * ($sinTh * $rx * $sinth3 - $cosTh * $ry * $costh3);
519 function calcVectorAngle($ux, $uy, $vx, $vy)
521 $ta = atan2($uy, $ux);
522 $tb = atan2($vy, $vx);
526 return 2 * M_PI
- ($ta - $tb);