commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-new / civicrm / vendor / phenx / php-svg-lib / src / Svg / Tag / Path.php
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 http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 */
8
9 namespace Svg\Tag;
10
11 use Svg\Surface\SurfaceInterface;
12
13 class Path extends Shape
14 {
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,
25 );
26
27 static $repeatedCommands = array(
28 'm' => 'l',
29 'M' => 'L',
30 );
31
32 public function start($attribs)
33 {
34 if (!isset($attribs['d'])) {
35 $this->hasShape = false;
36
37 return;
38 }
39
40 $commands = array();
41 preg_match_all('/([MZLHVCSQTAmzlhvcsqta])([eE ,\-.\d]+)*/', $attribs['d'], $commands, PREG_SET_ORDER);
42
43 $path = array();
44 foreach ($commands as $c) {
45 if (count($c) == 3) {
46 $arguments = array();
47 preg_match_all('/[\-^]?[\d.]+(e[\-]?[\d]+){0,1}/i', $c[2], $arguments, PREG_PATTERN_ORDER);
48
49 $item = $arguments[0];
50 $commandLower = strtolower($c[1]);
51
52 if (
53 isset(self::$commandLengths[$commandLower]) &&
54 ($commandLength = self::$commandLengths[$commandLower]) &&
55 count($item) > $commandLength
56 ) {
57 $repeatedCommand = isset(self::$repeatedCommands[$c[1]]) ? self::$repeatedCommands[$c[1]] : $c[1];
58 $command = $c[1];
59
60 for ($k = 0, $klen = count($item); $k < $klen; $k += $commandLength) {
61 $_item = array_slice($item, $k, $k + $commandLength);
62 array_unshift($_item, $command);
63 $path[] = $_item;
64
65 $command = $repeatedCommand;
66 }
67 } else {
68 array_unshift($item, $c[1]);
69 $path[] = $item;
70 }
71
72 } else {
73 $item = array($c[1]);
74
75 $path[] = $item;
76 }
77 }
78
79 $surface = $this->document->getSurface();
80
81 // From https://github.com/kangax/fabric.js/blob/master/src/shapes/path.class.js
82 $current = null; // current instruction
83 $previous = null;
84 $subpathStartX = 0;
85 $subpathStartY = 0;
86 $x = 0; // current x
87 $y = 0; // current y
88 $controlX = 0; // current control point x
89 $controlY = 0; // current control point y
90 $tempX = null;
91 $tempY = null;
92 $tempControlX = null;
93 $tempControlY = null;
94 $l = 0; //-((this.width / 2) + $this.pathOffset.x),
95 $t = 0; //-((this.height / 2) + $this.pathOffset.y),
96 $methodName = null;
97
98 foreach ($path as $current) {
99 switch ($current[0]) { // first letter
100 case 'l': // lineto, relative
101 $x += $current[1];
102 $y += $current[2];
103 $surface->lineTo($x + $l, $y + $t);
104 break;
105
106 case 'L': // lineto, absolute
107 $x = $current[1];
108 $y = $current[2];
109 $surface->lineTo($x + $l, $y + $t);
110 break;
111
112 case 'h': // horizontal lineto, relative
113 $x += $current[1];
114 $surface->lineTo($x + $l, $y + $t);
115 break;
116
117 case 'H': // horizontal lineto, absolute
118 $x = $current[1];
119 $surface->lineTo($x + $l, $y + $t);
120 break;
121
122 case 'v': // vertical lineto, relative
123 $y += $current[1];
124 $surface->lineTo($x + $l, $y + $t);
125 break;
126
127 case 'V': // verical lineto, absolute
128 $y = $current[1];
129 $surface->lineTo($x + $l, $y + $t);
130 break;
131
132 case 'm': // moveTo, relative
133 $x += $current[1];
134 $y += $current[2];
135 $subpathStartX = $x;
136 $subpathStartY = $y;
137 $surface->moveTo($x + $l, $y + $t);
138 break;
139
140 case 'M': // moveTo, absolute
141 $x = $current[1];
142 $y = $current[2];
143 $subpathStartX = $x;
144 $subpathStartY = $y;
145 $surface->moveTo($x + $l, $y + $t);
146 break;
147
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
158 $tempX + $l,
159 $tempY + $t
160 );
161 $x = $tempX;
162 $y = $tempY;
163 break;
164
165 case 'C': // bezierCurveTo, absolute
166 $x = $current[5];
167 $y = $current[6];
168 $controlX = $current[3];
169 $controlY = $current[4];
170 $surface->bezierCurveTo(
171 $current[1] + $l,
172 $current[2] + $t,
173 $controlX + $l,
174 $controlY + $t,
175 $x + $l,
176 $y + $t
177 );
178 break;
179
180 case 's': // shorthand cubic bezierCurveTo, relative
181
182 // transform to absolute x,y
183 $tempX = $x + $current[3];
184 $tempY = $y + $current[4];
185
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
189 $controlX = $x;
190 $controlY = $y;
191 } else {
192 // calculate reflection of previous control points
193 $controlX = 2 * $x - $controlX;
194 $controlY = 2 * $y - $controlY;
195 }
196
197 $surface->bezierCurveTo(
198 $controlX + $l,
199 $controlY + $t,
200 $x + $current[1] + $l,
201 $y + $current[2] + $t,
202 $tempX + $l,
203 $tempY + $t
204 );
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];
211
212 $x = $tempX;
213 $y = $tempY;
214 break;
215
216 case 'S': // shorthand cubic bezierCurveTo, absolute
217 $tempX = $current[3];
218 $tempY = $current[4];
219
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
223 $controlX = $x;
224 $controlY = $y;
225 } else {
226 // calculate reflection of previous control points
227 $controlX = 2 * $x - $controlX;
228 $controlY = 2 * $y - $controlY;
229 }
230
231 $surface->bezierCurveTo(
232 $controlX + $l,
233 $controlY + $t,
234 $current[1] + $l,
235 $current[2] + $t,
236 $tempX + $l,
237 $tempY + $t
238 );
239 $x = $tempX;
240 $y = $tempY;
241
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];
248
249 break;
250
251 case 'q': // quadraticCurveTo, relative
252 // transform to absolute x,y
253 $tempX = $x + $current[3];
254 $tempY = $y + $current[4];
255
256 $controlX = $x + $current[1];
257 $controlY = $y + $current[2];
258
259 $surface->quadraticCurveTo(
260 $controlX + $l,
261 $controlY + $t,
262 $tempX + $l,
263 $tempY + $t
264 );
265 $x = $tempX;
266 $y = $tempY;
267 break;
268
269 case 'Q': // quadraticCurveTo, absolute
270 $tempX = $current[3];
271 $tempY = $current[4];
272
273 $surface->quadraticCurveTo(
274 $current[1] + $l,
275 $current[2] + $t,
276 $tempX + $l,
277 $tempY + $t
278 );
279 $x = $tempX;
280 $y = $tempY;
281 $controlX = $current[1];
282 $controlY = $current[2];
283 break;
284
285 case 't': // shorthand quadraticCurveTo, relative
286
287 // transform to absolute x,y
288 $tempX = $x + $current[1];
289 $tempY = $y + $current[2];
290
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
294 $controlX = $x;
295 $controlY = $y;
296 } else {
297 if ($previous[0] === 't') {
298 // calculate reflection of previous control points for t
299 $controlX = 2 * $x - $tempControlX;
300 $controlY = 2 * $y - $tempControlY;
301 } else {
302 if ($previous[0] === 'q') {
303 // calculate reflection of previous control points for q
304 $controlX = 2 * $x - $controlX;
305 $controlY = 2 * $y - $controlY;
306 }
307 }
308 }
309
310 $tempControlX = $controlX;
311 $tempControlY = $controlY;
312
313 $surface->quadraticCurveTo(
314 $controlX + $l,
315 $controlY + $t,
316 $tempX + $l,
317 $tempY + $t
318 );
319 $x = $tempX;
320 $y = $tempY;
321 $controlX = $x + $current[1];
322 $controlY = $y + $current[2];
323 break;
324
325 case 'T':
326 $tempX = $current[1];
327 $tempY = $current[2];
328
329 // calculate reflection of previous control points
330 $controlX = 2 * $x - $controlX;
331 $controlY = 2 * $y - $controlY;
332 $surface->quadraticCurveTo(
333 $controlX + $l,
334 $controlY + $t,
335 $tempX + $l,
336 $tempY + $t
337 );
338 $x = $tempX;
339 $y = $tempY;
340 break;
341
342 case 'a':
343 // TODO: optimize this
344 $this->drawArc(
345 $surface,
346 $x + $l,
347 $y + $t,
348 array(
349 $current[1],
350 $current[2],
351 $current[3],
352 $current[4],
353 $current[5],
354 $current[6] + $x + $l,
355 $current[7] + $y + $t
356 )
357 );
358 $x += $current[6];
359 $y += $current[7];
360 break;
361
362 case 'A':
363 // TODO: optimize this
364 $this->drawArc(
365 $surface,
366 $x + $l,
367 $y + $t,
368 array(
369 $current[1],
370 $current[2],
371 $current[3],
372 $current[4],
373 $current[5],
374 $current[6] + $l,
375 $current[7] + $t
376 )
377 );
378 $x = $current[6];
379 $y = $current[7];
380 break;
381
382 case 'z':
383 case 'Z':
384 $x = $subpathStartX;
385 $y = $subpathStartY;
386 $surface->closePath();
387 break;
388 }
389 $previous = $current;
390 }
391 }
392
393 function drawArc(SurfaceInterface $surface, $fx, $fy, $coords)
394 {
395 $rx = $coords[0];
396 $ry = $coords[1];
397 $rot = $coords[2];
398 $large = $coords[3];
399 $sweep = $coords[4];
400 $tx = $coords[5];
401 $ty = $coords[6];
402 $segs = array(
403 array(),
404 array(),
405 array(),
406 array(),
407 );
408
409 $segsNorm = $this->arcToSegments($tx - $fx, $ty - $fy, $rx, $ry, $large, $sweep, $rot);
410
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;
418
419 call_user_func_array(array($surface, "bezierCurveTo"), $segs[$i]);
420 }
421 }
422
423 function arcToSegments($toX, $toY, $rx, $ry, $large, $sweep, $rotateX)
424 {
425 $th = $rotateX * M_PI / 180;
426 $sinTh = sin($th);
427 $cosTh = cos($th);
428 $fromX = 0;
429 $fromY = 0;
430
431 $rx = abs($rx);
432 $ry = abs($ry);
433
434 $px = -$cosTh * $toX * 0.5 - $sinTh * $toY * 0.5;
435 $py = -$cosTh * $toY * 0.5 + $sinTh * $toX * 0.5;
436 $rx2 = $rx * $rx;
437 $ry2 = $ry * $ry;
438 $py2 = $py * $py;
439 $px2 = $px * $px;
440 $pl = $rx2 * $ry2 - $rx2 * $py2 - $ry2 * $px2;
441 $root = 0;
442
443 if ($pl < 0) {
444 $s = sqrt(1 - $pl / ($rx2 * $ry2));
445 $rx *= $s;
446 $ry *= $s;
447 } else {
448 $root = ($large == $sweep ? -1.0 : 1.0) * sqrt($pl / ($rx2 * $py2 + $ry2 * $px2));
449 }
450
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);
457
458 if ($sweep == 0 && $dtheta > 0) {
459 $dtheta -= 2 * M_PI;
460 } else {
461 if ($sweep == 1 && $dtheta < 0) {
462 $dtheta += 2 * M_PI;
463 }
464 }
465
466 // $Convert $into $cubic $bezier $segments <= 90deg
467 $segments = ceil(abs($dtheta / M_PI * 2));
468 $result = array();
469 $mDelta = $dtheta / $segments;
470 $mT = 8 / 3 * sin($mDelta / 4) * sin($mDelta / 4) / sin($mDelta / 2);
471 $th3 = $mTheta + $mDelta;
472
473 for ($i = 0; $i < $segments; $i++) {
474 $result[$i] = $this->segmentToBezier(
475 $mTheta,
476 $th3,
477 $cosTh,
478 $sinTh,
479 $rx,
480 $ry,
481 $cx1,
482 $cy1,
483 $mT,
484 $fromX,
485 $fromY
486 );
487 $fromX = $result[$i][4];
488 $fromY = $result[$i][5];
489 $mTheta = $th3;
490 $th3 += $mDelta;
491 }
492
493 return $result;
494 }
495
496 function segmentToBezier($th2, $th3, $cosTh, $sinTh, $rx, $ry, $cx1, $cy1, $mT, $fromX, $fromY)
497 {
498 $costh2 = cos($th2);
499 $sinth2 = sin($th2);
500 $costh3 = cos($th3);
501 $sinth3 = sin($th3);
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);
508
509 return array(
510 $cp1X,
511 $cp1Y,
512 $cp2X,
513 $cp2Y,
514 $toX,
515 $toY
516 );
517 }
518
519 function calcVectorAngle($ux, $uy, $vx, $vy)
520 {
521 $ta = atan2($uy, $ux);
522 $tb = atan2($vy, $vx);
523 if ($tb >= $ta) {
524 return $tb - $ta;
525 } else {
526 return 2 * M_PI - ($ta - $tb);
527 }
528 }
529 }