/vendor/phenx/php-svg-lib/src/Svg/Tag/Path.php
https://gitlab.com/ealexis.t/kiosco · PHP · 529 lines · 430 code · 67 blank · 32 comment · 31 complexity · 1abf282974d4c69f665db80a14f1bad3 MD5 · raw file
- <?php
- /**
- * @package php-svg-lib
- * @link http://github.com/PhenX/php-svg-lib
- * @author Fabien Ménager <fabien.menager@gmail.com>
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
- */
- namespace Svg\Tag;
- use Svg\Surface\SurfaceInterface;
- class Path extends Shape
- {
- static $commandLengths = array(
- 'm' => 2,
- 'l' => 2,
- 'h' => 1,
- 'v' => 1,
- 'c' => 6,
- 's' => 4,
- 'q' => 4,
- 't' => 2,
- 'a' => 7,
- );
- static $repeatedCommands = array(
- 'm' => 'l',
- 'M' => 'L',
- );
- public function start($attribs)
- {
- if (!isset($attribs['d'])) {
- $this->hasShape = false;
- return;
- }
- $commands = array();
- preg_match_all('/([MZLHVCSQTAmzlhvcsqta])([eE ,\-.\d]+)*/', $attribs['d'], $commands, PREG_SET_ORDER);
- $path = array();
- foreach ($commands as $c) {
- if (count($c) == 3) {
- $arguments = array();
- preg_match_all('/[\-^]?[\d.]+(e[\-]?[\d]+){0,1}/i', $c[2], $arguments, PREG_PATTERN_ORDER);
- $item = $arguments[0];
- $commandLower = strtolower($c[1]);
- if (
- isset(self::$commandLengths[$commandLower]) &&
- ($commandLength = self::$commandLengths[$commandLower]) &&
- count($item) > $commandLength
- ) {
- $repeatedCommand = isset(self::$repeatedCommands[$c[1]]) ? self::$repeatedCommands[$c[1]] : $c[1];
- $command = $c[1];
- for ($k = 0, $klen = count($item); $k < $klen; $k += $commandLength) {
- $_item = array_slice($item, $k, $k + $commandLength);
- array_unshift($_item, $command);
- $path[] = $_item;
- $command = $repeatedCommand;
- }
- } else {
- array_unshift($item, $c[1]);
- $path[] = $item;
- }
- } else {
- $item = array($c[1]);
- $path[] = $item;
- }
- }
- $surface = $this->document->getSurface();
- // From https://github.com/kangax/fabric.js/blob/master/src/shapes/path.class.js
- $current = null; // current instruction
- $previous = null;
- $subpathStartX = 0;
- $subpathStartY = 0;
- $x = 0; // current x
- $y = 0; // current y
- $controlX = 0; // current control point x
- $controlY = 0; // current control point y
- $tempX = null;
- $tempY = null;
- $tempControlX = null;
- $tempControlY = null;
- $l = 0; //-((this.width / 2) + $this.pathOffset.x),
- $t = 0; //-((this.height / 2) + $this.pathOffset.y),
- $methodName = null;
- foreach ($path as $current) {
- switch ($current[0]) { // first letter
- case 'l': // lineto, relative
- $x += $current[1];
- $y += $current[2];
- $surface->lineTo($x + $l, $y + $t);
- break;
- case 'L': // lineto, absolute
- $x = $current[1];
- $y = $current[2];
- $surface->lineTo($x + $l, $y + $t);
- break;
- case 'h': // horizontal lineto, relative
- $x += $current[1];
- $surface->lineTo($x + $l, $y + $t);
- break;
- case 'H': // horizontal lineto, absolute
- $x = $current[1];
- $surface->lineTo($x + $l, $y + $t);
- break;
- case 'v': // vertical lineto, relative
- $y += $current[1];
- $surface->lineTo($x + $l, $y + $t);
- break;
- case 'V': // verical lineto, absolute
- $y = $current[1];
- $surface->lineTo($x + $l, $y + $t);
- break;
- case 'm': // moveTo, relative
- $x += $current[1];
- $y += $current[2];
- $subpathStartX = $x;
- $subpathStartY = $y;
- $surface->moveTo($x + $l, $y + $t);
- break;
- case 'M': // moveTo, absolute
- $x = $current[1];
- $y = $current[2];
- $subpathStartX = $x;
- $subpathStartY = $y;
- $surface->moveTo($x + $l, $y + $t);
- break;
- case 'c': // bezierCurveTo, relative
- $tempX = $x + $current[5];
- $tempY = $y + $current[6];
- $controlX = $x + $current[3];
- $controlY = $y + $current[4];
- $surface->bezierCurveTo(
- $x + $current[1] + $l, // x1
- $y + $current[2] + $t, // y1
- $controlX + $l, // x2
- $controlY + $t, // y2
- $tempX + $l,
- $tempY + $t
- );
- $x = $tempX;
- $y = $tempY;
- break;
- case 'C': // bezierCurveTo, absolute
- $x = $current[5];
- $y = $current[6];
- $controlX = $current[3];
- $controlY = $current[4];
- $surface->bezierCurveTo(
- $current[1] + $l,
- $current[2] + $t,
- $controlX + $l,
- $controlY + $t,
- $x + $l,
- $y + $t
- );
- break;
- case 's': // shorthand cubic bezierCurveTo, relative
- // transform to absolute x,y
- $tempX = $x + $current[3];
- $tempY = $y + $current[4];
- if (!preg_match('/[CcSs]/', $previous[0])) {
- // If there is no previous command or if the previous command was not a C, c, S, or s,
- // the control point is coincident with the current point
- $controlX = $x;
- $controlY = $y;
- } else {
- // calculate reflection of previous control points
- $controlX = 2 * $x - $controlX;
- $controlY = 2 * $y - $controlY;
- }
- $surface->bezierCurveTo(
- $controlX + $l,
- $controlY + $t,
- $x + $current[1] + $l,
- $y + $current[2] + $t,
- $tempX + $l,
- $tempY + $t
- );
- // set control point to 2nd one of this command
- // "... the first control point is assumed to be
- // the reflection of the second control point on
- // the previous command relative to the current point."
- $controlX = $x + $current[1];
- $controlY = $y + $current[2];
- $x = $tempX;
- $y = $tempY;
- break;
- case 'S': // shorthand cubic bezierCurveTo, absolute
- $tempX = $current[3];
- $tempY = $current[4];
- if (!preg_match('/[CcSs]/', $previous[0])) {
- // If there is no previous command or if the previous command was not a C, c, S, or s,
- // the control point is coincident with the current point
- $controlX = $x;
- $controlY = $y;
- } else {
- // calculate reflection of previous control points
- $controlX = 2 * $x - $controlX;
- $controlY = 2 * $y - $controlY;
- }
- $surface->bezierCurveTo(
- $controlX + $l,
- $controlY + $t,
- $current[1] + $l,
- $current[2] + $t,
- $tempX + $l,
- $tempY + $t
- );
- $x = $tempX;
- $y = $tempY;
- // set control point to 2nd one of this command
- // "... the first control point is assumed to be
- // the reflection of the second control point on
- // the previous command relative to the current point."
- $controlX = $current[1];
- $controlY = $current[2];
- break;
- case 'q': // quadraticCurveTo, relative
- // transform to absolute x,y
- $tempX = $x + $current[3];
- $tempY = $y + $current[4];
- $controlX = $x + $current[1];
- $controlY = $y + $current[2];
- $surface->quadraticCurveTo(
- $controlX + $l,
- $controlY + $t,
- $tempX + $l,
- $tempY + $t
- );
- $x = $tempX;
- $y = $tempY;
- break;
- case 'Q': // quadraticCurveTo, absolute
- $tempX = $current[3];
- $tempY = $current[4];
- $surface->quadraticCurveTo(
- $current[1] + $l,
- $current[2] + $t,
- $tempX + $l,
- $tempY + $t
- );
- $x = $tempX;
- $y = $tempY;
- $controlX = $current[1];
- $controlY = $current[2];
- break;
- case 't': // shorthand quadraticCurveTo, relative
- // transform to absolute x,y
- $tempX = $x + $current[1];
- $tempY = $y + $current[2];
- if (preg_match("/[QqTt]/", $previous[0])) {
- // If there is no previous command or if the previous command was not a Q, q, T or t,
- // assume the control point is coincident with the current point
- $controlX = $x;
- $controlY = $y;
- } else {
- if ($previous[0] === 't') {
- // calculate reflection of previous control points for t
- $controlX = 2 * $x - $tempControlX;
- $controlY = 2 * $y - $tempControlY;
- } else {
- if ($previous[0] === 'q') {
- // calculate reflection of previous control points for q
- $controlX = 2 * $x - $controlX;
- $controlY = 2 * $y - $controlY;
- }
- }
- }
- $tempControlX = $controlX;
- $tempControlY = $controlY;
- $surface->quadraticCurveTo(
- $controlX + $l,
- $controlY + $t,
- $tempX + $l,
- $tempY + $t
- );
- $x = $tempX;
- $y = $tempY;
- $controlX = $x + $current[1];
- $controlY = $y + $current[2];
- break;
- case 'T':
- $tempX = $current[1];
- $tempY = $current[2];
- // calculate reflection of previous control points
- $controlX = 2 * $x - $controlX;
- $controlY = 2 * $y - $controlY;
- $surface->quadraticCurveTo(
- $controlX + $l,
- $controlY + $t,
- $tempX + $l,
- $tempY + $t
- );
- $x = $tempX;
- $y = $tempY;
- break;
- case 'a':
- // TODO: optimize this
- $this->drawArc(
- $surface,
- $x + $l,
- $y + $t,
- array(
- $current[1],
- $current[2],
- $current[3],
- $current[4],
- $current[5],
- $current[6] + $x + $l,
- $current[7] + $y + $t
- )
- );
- $x += $current[6];
- $y += $current[7];
- break;
- case 'A':
- // TODO: optimize this
- $this->drawArc(
- $surface,
- $x + $l,
- $y + $t,
- array(
- $current[1],
- $current[2],
- $current[3],
- $current[4],
- $current[5],
- $current[6] + $l,
- $current[7] + $t
- )
- );
- $x = $current[6];
- $y = $current[7];
- break;
- case 'z':
- case 'Z':
- $x = $subpathStartX;
- $y = $subpathStartY;
- $surface->closePath();
- break;
- }
- $previous = $current;
- }
- }
- function drawArc(SurfaceInterface $surface, $fx, $fy, $coords)
- {
- $rx = $coords[0];
- $ry = $coords[1];
- $rot = $coords[2];
- $large = $coords[3];
- $sweep = $coords[4];
- $tx = $coords[5];
- $ty = $coords[6];
- $segs = array(
- array(),
- array(),
- array(),
- array(),
- );
- $segsNorm = $this->arcToSegments($tx - $fx, $ty - $fy, $rx, $ry, $large, $sweep, $rot);
- for ($i = 0, $len = count($segsNorm); $i < $len; $i++) {
- $segs[$i][0] = $segsNorm[$i][0] + $fx;
- $segs[$i][1] = $segsNorm[$i][1] + $fy;
- $segs[$i][2] = $segsNorm[$i][2] + $fx;
- $segs[$i][3] = $segsNorm[$i][3] + $fy;
- $segs[$i][4] = $segsNorm[$i][4] + $fx;
- $segs[$i][5] = $segsNorm[$i][5] + $fy;
- call_user_func_array(array($surface, "bezierCurveTo"), $segs[$i]);
- }
- }
- function arcToSegments($toX, $toY, $rx, $ry, $large, $sweep, $rotateX)
- {
- $th = $rotateX * M_PI / 180;
- $sinTh = sin($th);
- $cosTh = cos($th);
- $fromX = 0;
- $fromY = 0;
- $rx = abs($rx);
- $ry = abs($ry);
- $px = -$cosTh * $toX * 0.5 - $sinTh * $toY * 0.5;
- $py = -$cosTh * $toY * 0.5 + $sinTh * $toX * 0.5;
- $rx2 = $rx * $rx;
- $ry2 = $ry * $ry;
- $py2 = $py * $py;
- $px2 = $px * $px;
- $pl = $rx2 * $ry2 - $rx2 * $py2 - $ry2 * $px2;
- $root = 0;
- if ($pl < 0) {
- $s = sqrt(1 - $pl / ($rx2 * $ry2));
- $rx *= $s;
- $ry *= $s;
- } else {
- $root = ($large == $sweep ? -1.0 : 1.0) * sqrt($pl / ($rx2 * $py2 + $ry2 * $px2));
- }
- $cx = $root * $rx * $py / $ry;
- $cy = -$root * $ry * $px / $rx;
- $cx1 = $cosTh * $cx - $sinTh * $cy + $toX * 0.5;
- $cy1 = $sinTh * $cx + $cosTh * $cy + $toY * 0.5;
- $mTheta = $this->calcVectorAngle(1, 0, ($px - $cx) / $rx, ($py - $cy) / $ry);
- $dtheta = $this->calcVectorAngle(($px - $cx) / $rx, ($py - $cy) / $ry, (-$px - $cx) / $rx, (-$py - $cy) / $ry);
- if ($sweep == 0 && $dtheta > 0) {
- $dtheta -= 2 * M_PI;
- } else {
- if ($sweep == 1 && $dtheta < 0) {
- $dtheta += 2 * M_PI;
- }
- }
- // $Convert $into $cubic $bezier $segments <= 90deg
- $segments = ceil(abs($dtheta / M_PI * 2));
- $result = array();
- $mDelta = $dtheta / $segments;
- $mT = 8 / 3 * sin($mDelta / 4) * sin($mDelta / 4) / sin($mDelta / 2);
- $th3 = $mTheta + $mDelta;
- for ($i = 0; $i < $segments; $i++) {
- $result[$i] = $this->segmentToBezier(
- $mTheta,
- $th3,
- $cosTh,
- $sinTh,
- $rx,
- $ry,
- $cx1,
- $cy1,
- $mT,
- $fromX,
- $fromY
- );
- $fromX = $result[$i][4];
- $fromY = $result[$i][5];
- $mTheta = $th3;
- $th3 += $mDelta;
- }
- return $result;
- }
- function segmentToBezier($th2, $th3, $cosTh, $sinTh, $rx, $ry, $cx1, $cy1, $mT, $fromX, $fromY)
- {
- $costh2 = cos($th2);
- $sinth2 = sin($th2);
- $costh3 = cos($th3);
- $sinth3 = sin($th3);
- $toX = $cosTh * $rx * $costh3 - $sinTh * $ry * $sinth3 + $cx1;
- $toY = $sinTh * $rx * $costh3 + $cosTh * $ry * $sinth3 + $cy1;
- $cp1X = $fromX + $mT * (-$cosTh * $rx * $sinth2 - $sinTh * $ry * $costh2);
- $cp1Y = $fromY + $mT * (-$sinTh * $rx * $sinth2 + $cosTh * $ry * $costh2);
- $cp2X = $toX + $mT * ($cosTh * $rx * $sinth3 + $sinTh * $ry * $costh3);
- $cp2Y = $toY + $mT * ($sinTh * $rx * $sinth3 - $cosTh * $ry * $costh3);
- return array(
- $cp1X,
- $cp1Y,
- $cp2X,
- $cp2Y,
- $toX,
- $toY
- );
- }
- function calcVectorAngle($ux, $uy, $vx, $vy)
- {
- $ta = atan2($uy, $ux);
- $tb = atan2($vy, $vx);
- if ($tb >= $ta) {
- return $tb - $ta;
- } else {
- return 2 * M_PI - ($ta - $tb);
- }
- }
- }