summaryrefslogtreecommitdiffstats
path: root/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Path.php
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Path.php576
1 files changed, 576 insertions, 0 deletions
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Path.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Path.php
new file mode 100644
index 0000000..3dce7a6
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Path.php
@@ -0,0 +1,576 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+use Svg\Surface\SurfaceInterface;
+
+class Path extends Shape
+{
+ // kindly borrowed from fabric.util.parsePath.
+ /* @see https://github.com/fabricjs/fabric.js/blob/master/src/util/path.js#L664 */
+ const NUMBER_PATTERN = '([-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?)\s*';
+ const COMMA_PATTERN = '(?:\s+,?\s*|,\s*)?';
+ const FLAG_PATTERN = '([01])';
+ const ARC_REGEXP = '/'
+ . self::NUMBER_PATTERN
+ . self::COMMA_PATTERN
+ . self::NUMBER_PATTERN
+ . self::COMMA_PATTERN
+ . self::NUMBER_PATTERN
+ . self::COMMA_PATTERN
+ . self::FLAG_PATTERN
+ . self::COMMA_PATTERN
+ . self::FLAG_PATTERN
+ . self::COMMA_PATTERN
+ . self::NUMBER_PATTERN
+ . self::COMMA_PATTERN
+ . self::NUMBER_PATTERN
+ . '/';
+
+ 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 static function parse(string $commandSequence): array
+ {
+ $commands = array();
+ preg_match_all('/([MZLHVCSQTAmzlhvcsqta])([eE ,\-.\d]+)*/', $commandSequence, $commands, PREG_SET_ORDER);
+
+ $path = array();
+ foreach ($commands as $c) {
+ if (count($c) == 3) {
+ $commandLower = strtolower($c[1]);
+
+ // arcs have special flags that apparently don't require spaces.
+ if ($commandLower === 'a' && preg_match_all(static::ARC_REGEXP, $c[2], $matches, PREG_PATTERN_ORDER)) {
+ $numberOfMatches = count($matches[0]);
+ for ($k = 0; $k < $numberOfMatches; ++$k) {
+ $path[] = [
+ $c[1],
+ $matches[1][$k],
+ $matches[2][$k],
+ $matches[3][$k],
+ $matches[4][$k],
+ $matches[5][$k],
+ $matches[6][$k],
+ $matches[7][$k],
+ ];
+ }
+ continue;
+ }
+
+ $arguments = array();
+ preg_match_all('/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/i', $c[2], $arguments, PREG_PATTERN_ORDER);
+ $item = $arguments[0];
+
+ 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;
+ }
+ }
+
+ return $path;
+ }
+
+ public function start($attributes)
+ {
+ if (!isset($attributes['d'])) {
+ $this->hasShape = false;
+
+ return;
+ }
+
+ $path = static::parse($attributes['d']);
+ $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),
+
+ 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];
+
+ // calculate reflection of previous control points
+ if (preg_match('/[QqT]/', $previous[0])) {
+ $controlX = 2 * $x - $controlX;
+ $controlY = 2 * $y - $controlY;
+ } elseif ($previous[0] === 't') {
+ $controlX = 2 * $x - $tempControlX;
+ $controlY = 2 * $y - $tempControlY;
+ } else {
+ $controlX = $x;
+ $controlY = $y;
+ }
+
+ $tempControlX = $controlX;
+ $tempControlY = $controlY;
+
+ $surface->quadraticCurveTo(
+ $controlX + $l,
+ $controlY + $t,
+ $tempX + $l,
+ $tempY + $t
+ );
+ $x = $tempX;
+ $y = $tempY;
+ break;
+
+ case 'T':
+ $tempX = $current[1];
+ $tempY = $current[2];
+
+ // calculate reflection of previous control points
+ if (preg_match('/[QqTt]/', $previous[0])) {
+ $controlX = 2 * $x - $controlX;
+ $controlY = 2 * $y - $controlY;
+ } else {
+ $controlX = $x;
+ $controlY = $y;
+ }
+
+ $surface->quadraticCurveTo(
+ $controlX + $l,
+ $controlY + $t,
+ $tempX + $l,
+ $tempY + $t
+ );
+ $x = $tempX;
+ $y = $tempY;
+ break;
+
+ case 'a':
+ $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(),
+ );
+
+ $toX = $tx - $fx;
+ $toY = $ty - $fy;
+
+ if ($toX + $toY === 0) {
+ return;
+ }
+
+ $segsNorm = $this->arcToSegments($toX, $toY, $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);
+ }
+ }
+}