PageRenderTime 47ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/phenx/php-svg-lib/src/Svg/Tag/Path.php

https://bitbucket.org/openemr/openemr
PHP | 529 lines | 430 code | 67 blank | 32 comment | 31 complexity | 9ac363f329d770e9e8d9ae9b55b7a5be MD5 | raw file
Possible License(s): Apache-2.0, AGPL-1.0, GPL-2.0, LGPL-3.0, BSD-3-Clause, Unlicense, MPL-2.0, GPL-3.0, LGPL-2.1
  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. namespace Svg\Tag;
  9. use Svg\Surface\SurfaceInterface;
  10. class Path extends Shape
  11. {
  12. static $commandLengths = array(
  13. 'm' => 2,
  14. 'l' => 2,
  15. 'h' => 1,
  16. 'v' => 1,
  17. 'c' => 6,
  18. 's' => 4,
  19. 'q' => 4,
  20. 't' => 2,
  21. 'a' => 7,
  22. );
  23. static $repeatedCommands = array(
  24. 'm' => 'l',
  25. 'M' => 'L',
  26. );
  27. public function start($attribs)
  28. {
  29. if (!isset($attribs['d'])) {
  30. $this->hasShape = false;
  31. return;
  32. }
  33. $commands = array();
  34. preg_match_all('/([MZLHVCSQTAmzlhvcsqta])([eE ,\-.\d]+)*/', $attribs['d'], $commands, PREG_SET_ORDER);
  35. $path = array();
  36. foreach ($commands as $c) {
  37. if (count($c) == 3) {
  38. $arguments = array();
  39. preg_match_all('/[\-^]?[\d.]+(e[\-]?[\d]+){0,1}/i', $c[2], $arguments, PREG_PATTERN_ORDER);
  40. $item = $arguments[0];
  41. $commandLower = strtolower($c[1]);
  42. if (
  43. isset(self::$commandLengths[$commandLower]) &&
  44. ($commandLength = self::$commandLengths[$commandLower]) &&
  45. count($item) > $commandLength
  46. ) {
  47. $repeatedCommand = isset(self::$repeatedCommands[$c[1]]) ? self::$repeatedCommands[$c[1]] : $c[1];
  48. $command = $c[1];
  49. for ($k = 0, $klen = count($item); $k < $klen; $k += $commandLength) {
  50. $_item = array_slice($item, $k, $k + $commandLength);
  51. array_unshift($_item, $command);
  52. $path[] = $_item;
  53. $command = $repeatedCommand;
  54. }
  55. } else {
  56. array_unshift($item, $c[1]);
  57. $path[] = $item;
  58. }
  59. } else {
  60. $item = array($c[1]);
  61. $path[] = $item;
  62. }
  63. }
  64. $surface = $this->document->getSurface();
  65. // From https://github.com/kangax/fabric.js/blob/master/src/shapes/path.class.js
  66. $current = null; // current instruction
  67. $previous = null;
  68. $subpathStartX = 0;
  69. $subpathStartY = 0;
  70. $x = 0; // current x
  71. $y = 0; // current y
  72. $controlX = 0; // current control point x
  73. $controlY = 0; // current control point y
  74. $tempX = null;
  75. $tempY = null;
  76. $tempControlX = null;
  77. $tempControlY = null;
  78. $l = 0; //-((this.width / 2) + $this.pathOffset.x),
  79. $t = 0; //-((this.height / 2) + $this.pathOffset.y),
  80. $methodName = null;
  81. foreach ($path as $current) {
  82. switch ($current[0]) { // first letter
  83. case 'l': // lineto, relative
  84. $x += $current[1];
  85. $y += $current[2];
  86. $surface->lineTo($x + $l, $y + $t);
  87. break;
  88. case 'L': // lineto, absolute
  89. $x = $current[1];
  90. $y = $current[2];
  91. $surface->lineTo($x + $l, $y + $t);
  92. break;
  93. case 'h': // horizontal lineto, relative
  94. $x += $current[1];
  95. $surface->lineTo($x + $l, $y + $t);
  96. break;
  97. case 'H': // horizontal lineto, absolute
  98. $x = $current[1];
  99. $surface->lineTo($x + $l, $y + $t);
  100. break;
  101. case 'v': // vertical lineto, relative
  102. $y += $current[1];
  103. $surface->lineTo($x + $l, $y + $t);
  104. break;
  105. case 'V': // verical lineto, absolute
  106. $y = $current[1];
  107. $surface->lineTo($x + $l, $y + $t);
  108. break;
  109. case 'm': // moveTo, relative
  110. $x += $current[1];
  111. $y += $current[2];
  112. $subpathStartX = $x;
  113. $subpathStartY = $y;
  114. $surface->moveTo($x + $l, $y + $t);
  115. break;
  116. case 'M': // moveTo, absolute
  117. $x = $current[1];
  118. $y = $current[2];
  119. $subpathStartX = $x;
  120. $subpathStartY = $y;
  121. $surface->moveTo($x + $l, $y + $t);
  122. break;
  123. case 'c': // bezierCurveTo, relative
  124. $tempX = $x + $current[5];
  125. $tempY = $y + $current[6];
  126. $controlX = $x + $current[3];
  127. $controlY = $y + $current[4];
  128. $surface->bezierCurveTo(
  129. $x + $current[1] + $l, // x1
  130. $y + $current[2] + $t, // y1
  131. $controlX + $l, // x2
  132. $controlY + $t, // y2
  133. $tempX + $l,
  134. $tempY + $t
  135. );
  136. $x = $tempX;
  137. $y = $tempY;
  138. break;
  139. case 'C': // bezierCurveTo, absolute
  140. $x = $current[5];
  141. $y = $current[6];
  142. $controlX = $current[3];
  143. $controlY = $current[4];
  144. $surface->bezierCurveTo(
  145. $current[1] + $l,
  146. $current[2] + $t,
  147. $controlX + $l,
  148. $controlY + $t,
  149. $x + $l,
  150. $y + $t
  151. );
  152. break;
  153. case 's': // shorthand cubic bezierCurveTo, relative
  154. // transform to absolute x,y
  155. $tempX = $x + $current[3];
  156. $tempY = $y + $current[4];
  157. if (!preg_match('/[CcSs]/', $previous[0])) {
  158. // If there is no previous command or if the previous command was not a C, c, S, or s,
  159. // the control point is coincident with the current point
  160. $controlX = $x;
  161. $controlY = $y;
  162. } else {
  163. // calculate reflection of previous control points
  164. $controlX = 2 * $x - $controlX;
  165. $controlY = 2 * $y - $controlY;
  166. }
  167. $surface->bezierCurveTo(
  168. $controlX + $l,
  169. $controlY + $t,
  170. $x + $current[1] + $l,
  171. $y + $current[2] + $t,
  172. $tempX + $l,
  173. $tempY + $t
  174. );
  175. // set control point to 2nd one of this command
  176. // "... the first control point is assumed to be
  177. // the reflection of the second control point on
  178. // the previous command relative to the current point."
  179. $controlX = $x + $current[1];
  180. $controlY = $y + $current[2];
  181. $x = $tempX;
  182. $y = $tempY;
  183. break;
  184. case 'S': // shorthand cubic bezierCurveTo, absolute
  185. $tempX = $current[3];
  186. $tempY = $current[4];
  187. if (!preg_match('/[CcSs]/', $previous[0])) {
  188. // If there is no previous command or if the previous command was not a C, c, S, or s,
  189. // the control point is coincident with the current point
  190. $controlX = $x;
  191. $controlY = $y;
  192. } else {
  193. // calculate reflection of previous control points
  194. $controlX = 2 * $x - $controlX;
  195. $controlY = 2 * $y - $controlY;
  196. }
  197. $surface->bezierCurveTo(
  198. $controlX + $l,
  199. $controlY + $t,
  200. $current[1] + $l,
  201. $current[2] + $t,
  202. $tempX + $l,
  203. $tempY + $t
  204. );
  205. $x = $tempX;
  206. $y = $tempY;
  207. // set control point to 2nd one of this command
  208. // "... the first control point is assumed to be
  209. // the reflection of the second control point on
  210. // the previous command relative to the current point."
  211. $controlX = $current[1];
  212. $controlY = $current[2];
  213. break;
  214. case 'q': // quadraticCurveTo, relative
  215. // transform to absolute x,y
  216. $tempX = $x + $current[3];
  217. $tempY = $y + $current[4];
  218. $controlX = $x + $current[1];
  219. $controlY = $y + $current[2];
  220. $surface->quadraticCurveTo(
  221. $controlX + $l,
  222. $controlY + $t,
  223. $tempX + $l,
  224. $tempY + $t
  225. );
  226. $x = $tempX;
  227. $y = $tempY;
  228. break;
  229. case 'Q': // quadraticCurveTo, absolute
  230. $tempX = $current[3];
  231. $tempY = $current[4];
  232. $surface->quadraticCurveTo(
  233. $current[1] + $l,
  234. $current[2] + $t,
  235. $tempX + $l,
  236. $tempY + $t
  237. );
  238. $x = $tempX;
  239. $y = $tempY;
  240. $controlX = $current[1];
  241. $controlY = $current[2];
  242. break;
  243. case 't': // shorthand quadraticCurveTo, relative
  244. // transform to absolute x,y
  245. $tempX = $x + $current[1];
  246. $tempY = $y + $current[2];
  247. if (preg_match("/[QqTt]/", $previous[0])) {
  248. // If there is no previous command or if the previous command was not a Q, q, T or t,
  249. // assume the control point is coincident with the current point
  250. $controlX = $x;
  251. $controlY = $y;
  252. } else {
  253. if ($previous[0] === 't') {
  254. // calculate reflection of previous control points for t
  255. $controlX = 2 * $x - $tempControlX;
  256. $controlY = 2 * $y - $tempControlY;
  257. } else {
  258. if ($previous[0] === 'q') {
  259. // calculate reflection of previous control points for q
  260. $controlX = 2 * $x - $controlX;
  261. $controlY = 2 * $y - $controlY;
  262. }
  263. }
  264. }
  265. $tempControlX = $controlX;
  266. $tempControlY = $controlY;
  267. $surface->quadraticCurveTo(
  268. $controlX + $l,
  269. $controlY + $t,
  270. $tempX + $l,
  271. $tempY + $t
  272. );
  273. $x = $tempX;
  274. $y = $tempY;
  275. $controlX = $x + $current[1];
  276. $controlY = $y + $current[2];
  277. break;
  278. case 'T':
  279. $tempX = $current[1];
  280. $tempY = $current[2];
  281. // calculate reflection of previous control points
  282. $controlX = 2 * $x - $controlX;
  283. $controlY = 2 * $y - $controlY;
  284. $surface->quadraticCurveTo(
  285. $controlX + $l,
  286. $controlY + $t,
  287. $tempX + $l,
  288. $tempY + $t
  289. );
  290. $x = $tempX;
  291. $y = $tempY;
  292. break;
  293. case 'a':
  294. // TODO: optimize this
  295. $this->drawArc(
  296. $surface,
  297. $x + $l,
  298. $y + $t,
  299. array(
  300. $current[1],
  301. $current[2],
  302. $current[3],
  303. $current[4],
  304. $current[5],
  305. $current[6] + $x + $l,
  306. $current[7] + $y + $t
  307. )
  308. );
  309. $x += $current[6];
  310. $y += $current[7];
  311. break;
  312. case 'A':
  313. // TODO: optimize this
  314. $this->drawArc(
  315. $surface,
  316. $x + $l,
  317. $y + $t,
  318. array(
  319. $current[1],
  320. $current[2],
  321. $current[3],
  322. $current[4],
  323. $current[5],
  324. $current[6] + $l,
  325. $current[7] + $t
  326. )
  327. );
  328. $x = $current[6];
  329. $y = $current[7];
  330. break;
  331. case 'z':
  332. case 'Z':
  333. $x = $subpathStartX;
  334. $y = $subpathStartY;
  335. $surface->closePath();
  336. break;
  337. }
  338. $previous = $current;
  339. }
  340. }
  341. function drawArc(SurfaceInterface $surface, $fx, $fy, $coords)
  342. {
  343. $rx = $coords[0];
  344. $ry = $coords[1];
  345. $rot = $coords[2];
  346. $large = $coords[3];
  347. $sweep = $coords[4];
  348. $tx = $coords[5];
  349. $ty = $coords[6];
  350. $segs = array(
  351. array(),
  352. array(),
  353. array(),
  354. array(),
  355. );
  356. $segsNorm = $this->arcToSegments($tx - $fx, $ty - $fy, $rx, $ry, $large, $sweep, $rot);
  357. for ($i = 0, $len = count($segsNorm); $i < $len; $i++) {
  358. $segs[$i][0] = $segsNorm[$i][0] + $fx;
  359. $segs[$i][1] = $segsNorm[$i][1] + $fy;
  360. $segs[$i][2] = $segsNorm[$i][2] + $fx;
  361. $segs[$i][3] = $segsNorm[$i][3] + $fy;
  362. $segs[$i][4] = $segsNorm[$i][4] + $fx;
  363. $segs[$i][5] = $segsNorm[$i][5] + $fy;
  364. call_user_func_array(array($surface, "bezierCurveTo"), $segs[$i]);
  365. }
  366. }
  367. function arcToSegments($toX, $toY, $rx, $ry, $large, $sweep, $rotateX)
  368. {
  369. $th = $rotateX * M_PI / 180;
  370. $sinTh = sin($th);
  371. $cosTh = cos($th);
  372. $fromX = 0;
  373. $fromY = 0;
  374. $rx = abs($rx);
  375. $ry = abs($ry);
  376. $px = -$cosTh * $toX * 0.5 - $sinTh * $toY * 0.5;
  377. $py = -$cosTh * $toY * 0.5 + $sinTh * $toX * 0.5;
  378. $rx2 = $rx * $rx;
  379. $ry2 = $ry * $ry;
  380. $py2 = $py * $py;
  381. $px2 = $px * $px;
  382. $pl = $rx2 * $ry2 - $rx2 * $py2 - $ry2 * $px2;
  383. $root = 0;
  384. if ($pl < 0) {
  385. $s = sqrt(1 - $pl / ($rx2 * $ry2));
  386. $rx *= $s;
  387. $ry *= $s;
  388. } else {
  389. $root = ($large == $sweep ? -1.0 : 1.0) * sqrt($pl / ($rx2 * $py2 + $ry2 * $px2));
  390. }
  391. $cx = $root * $rx * $py / $ry;
  392. $cy = -$root * $ry * $px / $rx;
  393. $cx1 = $cosTh * $cx - $sinTh * $cy + $toX * 0.5;
  394. $cy1 = $sinTh * $cx + $cosTh * $cy + $toY * 0.5;
  395. $mTheta = $this->calcVectorAngle(1, 0, ($px - $cx) / $rx, ($py - $cy) / $ry);
  396. $dtheta = $this->calcVectorAngle(($px - $cx) / $rx, ($py - $cy) / $ry, (-$px - $cx) / $rx, (-$py - $cy) / $ry);
  397. if ($sweep == 0 && $dtheta > 0) {
  398. $dtheta -= 2 * M_PI;
  399. } else {
  400. if ($sweep == 1 && $dtheta < 0) {
  401. $dtheta += 2 * M_PI;
  402. }
  403. }
  404. // $Convert $into $cubic $bezier $segments <= 90deg
  405. $segments = ceil(abs($dtheta / M_PI * 2));
  406. $result = array();
  407. $mDelta = $dtheta / $segments;
  408. $mT = 8 / 3 * sin($mDelta / 4) * sin($mDelta / 4) / sin($mDelta / 2);
  409. $th3 = $mTheta + $mDelta;
  410. for ($i = 0; $i < $segments; $i++) {
  411. $result[$i] = $this->segmentToBezier(
  412. $mTheta,
  413. $th3,
  414. $cosTh,
  415. $sinTh,
  416. $rx,
  417. $ry,
  418. $cx1,
  419. $cy1,
  420. $mT,
  421. $fromX,
  422. $fromY
  423. );
  424. $fromX = $result[$i][4];
  425. $fromY = $result[$i][5];
  426. $mTheta = $th3;
  427. $th3 += $mDelta;
  428. }
  429. return $result;
  430. }
  431. function segmentToBezier($th2, $th3, $cosTh, $sinTh, $rx, $ry, $cx1, $cy1, $mT, $fromX, $fromY)
  432. {
  433. $costh2 = cos($th2);
  434. $sinth2 = sin($th2);
  435. $costh3 = cos($th3);
  436. $sinth3 = sin($th3);
  437. $toX = $cosTh * $rx * $costh3 - $sinTh * $ry * $sinth3 + $cx1;
  438. $toY = $sinTh * $rx * $costh3 + $cosTh * $ry * $sinth3 + $cy1;
  439. $cp1X = $fromX + $mT * (-$cosTh * $rx * $sinth2 - $sinTh * $ry * $costh2);
  440. $cp1Y = $fromY + $mT * (-$sinTh * $rx * $sinth2 + $cosTh * $ry * $costh2);
  441. $cp2X = $toX + $mT * ($cosTh * $rx * $sinth3 + $sinTh * $ry * $costh3);
  442. $cp2Y = $toY + $mT * ($sinTh * $rx * $sinth3 - $cosTh * $ry * $costh3);
  443. return array(
  444. $cp1X,
  445. $cp1Y,
  446. $cp2X,
  447. $cp2Y,
  448. $toX,
  449. $toY
  450. );
  451. }
  452. function calcVectorAngle($ux, $uy, $vx, $vy)
  453. {
  454. $ta = atan2($uy, $ux);
  455. $tb = atan2($vy, $vx);
  456. if ($tb >= $ta) {
  457. return $tb - $ta;
  458. } else {
  459. return 2 * M_PI - ($ta - $tb);
  460. }
  461. }
  462. }