| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 | 
							- <?php
 
- declare(strict_types = 1);
 
- namespace BaconQrCode\Renderer\Image;
 
- use BaconQrCode\Exception\RuntimeException;
 
- use BaconQrCode\Renderer\Color\Alpha;
 
- use BaconQrCode\Renderer\Color\ColorInterface;
 
- use BaconQrCode\Renderer\Path\Close;
 
- use BaconQrCode\Renderer\Path\Curve;
 
- use BaconQrCode\Renderer\Path\EllipticArc;
 
- use BaconQrCode\Renderer\Path\Line;
 
- use BaconQrCode\Renderer\Path\Move;
 
- use BaconQrCode\Renderer\Path\Path;
 
- use BaconQrCode\Renderer\RendererStyle\Gradient;
 
- use BaconQrCode\Renderer\RendererStyle\GradientType;
 
- use XMLWriter;
 
- final class SvgImageBackEnd implements ImageBackEndInterface
 
- {
 
-     private const PRECISION = 3;
 
-     /**
 
-      * @var XMLWriter|null
 
-      */
 
-     private $xmlWriter;
 
-     /**
 
-      * @var int[]|null
 
-      */
 
-     private $stack;
 
-     /**
 
-      * @var int|null
 
-      */
 
-     private $currentStack;
 
-     /**
 
-      * @var int|null
 
-      */
 
-     private $gradientCount;
 
-     public function __construct()
 
-     {
 
-         if (! class_exists(XMLWriter::class)) {
 
-             throw new RuntimeException('You need to install the libxml extension to use this back end');
 
-         }
 
-     }
 
-     public function new(int $size, ColorInterface $backgroundColor) : void
 
-     {
 
-         $this->xmlWriter = new XMLWriter();
 
-         $this->xmlWriter->openMemory();
 
-         $this->xmlWriter->startDocument('1.0', 'UTF-8');
 
-         $this->xmlWriter->startElement('svg');
 
-         $this->xmlWriter->writeAttribute('xmlns', 'http://www.w3.org/2000/svg');
 
-         $this->xmlWriter->writeAttribute('version', '1.1');
 
-         $this->xmlWriter->writeAttribute('width', (string) $size);
 
-         $this->xmlWriter->writeAttribute('height', (string) $size);
 
-         $this->xmlWriter->writeAttribute('viewBox', '0 0 '. $size . ' ' . $size);
 
-         $this->gradientCount = 0;
 
-         $this->currentStack = 0;
 
-         $this->stack[0] = 0;
 
-         $alpha = 1;
 
-         if ($backgroundColor instanceof Alpha) {
 
-             $alpha = $backgroundColor->getAlpha() / 100;
 
-         }
 
-         if (0 === $alpha) {
 
-             return;
 
-         }
 
-         $this->xmlWriter->startElement('rect');
 
-         $this->xmlWriter->writeAttribute('x', '0');
 
-         $this->xmlWriter->writeAttribute('y', '0');
 
-         $this->xmlWriter->writeAttribute('width', (string) $size);
 
-         $this->xmlWriter->writeAttribute('height', (string) $size);
 
-         $this->xmlWriter->writeAttribute('fill', $this->getColorString($backgroundColor));
 
-         if ($alpha < 1) {
 
-             $this->xmlWriter->writeAttribute('fill-opacity', (string) $alpha);
 
-         }
 
-         $this->xmlWriter->endElement();
 
-     }
 
-     public function scale(float $size) : void
 
-     {
 
-         if (null === $this->xmlWriter) {
 
-             throw new RuntimeException('No image has been started');
 
-         }
 
-         $this->xmlWriter->startElement('g');
 
-         $this->xmlWriter->writeAttribute(
 
-             'transform',
 
-             sprintf('scale(%s)', round($size, self::PRECISION))
 
-         );
 
-         ++$this->stack[$this->currentStack];
 
-     }
 
-     public function translate(float $x, float $y) : void
 
-     {
 
-         if (null === $this->xmlWriter) {
 
-             throw new RuntimeException('No image has been started');
 
-         }
 
-         $this->xmlWriter->startElement('g');
 
-         $this->xmlWriter->writeAttribute(
 
-             'transform',
 
-             sprintf('translate(%s,%s)', round($x, self::PRECISION), round($y, self::PRECISION))
 
-         );
 
-         ++$this->stack[$this->currentStack];
 
-     }
 
-     public function rotate(int $degrees) : void
 
-     {
 
-         if (null === $this->xmlWriter) {
 
-             throw new RuntimeException('No image has been started');
 
-         }
 
-         $this->xmlWriter->startElement('g');
 
-         $this->xmlWriter->writeAttribute('transform', sprintf('rotate(%d)', $degrees));
 
-         ++$this->stack[$this->currentStack];
 
-     }
 
-     public function push() : void
 
-     {
 
-         if (null === $this->xmlWriter) {
 
-             throw new RuntimeException('No image has been started');
 
-         }
 
-         $this->xmlWriter->startElement('g');
 
-         $this->stack[] = 1;
 
-         ++$this->currentStack;
 
-     }
 
-     public function pop() : void
 
-     {
 
-         if (null === $this->xmlWriter) {
 
-             throw new RuntimeException('No image has been started');
 
-         }
 
-         for ($i = 0; $i < $this->stack[$this->currentStack]; ++$i) {
 
-             $this->xmlWriter->endElement();
 
-         }
 
-         array_pop($this->stack);
 
-         --$this->currentStack;
 
-     }
 
-     public function drawPathWithColor(Path $path, ColorInterface $color) : void
 
-     {
 
-         if (null === $this->xmlWriter) {
 
-             throw new RuntimeException('No image has been started');
 
-         }
 
-         $alpha = 1;
 
-         if ($color instanceof Alpha) {
 
-             $alpha = $color->getAlpha() / 100;
 
-         }
 
-         $this->startPathElement($path);
 
-         $this->xmlWriter->writeAttribute('fill', $this->getColorString($color));
 
-         if ($alpha < 1) {
 
-             $this->xmlWriter->writeAttribute('fill-opacity', (string) $alpha);
 
-         }
 
-         $this->xmlWriter->endElement();
 
-     }
 
-     public function drawPathWithGradient(
 
-         Path $path,
 
-         Gradient $gradient,
 
-         float $x,
 
-         float $y,
 
-         float $width,
 
-         float $height
 
-     ) : void {
 
-         if (null === $this->xmlWriter) {
 
-             throw new RuntimeException('No image has been started');
 
-         }
 
-         $gradientId = $this->createGradientFill($gradient, $x, $y, $width, $height);
 
-         $this->startPathElement($path);
 
-         $this->xmlWriter->writeAttribute('fill', 'url(#' . $gradientId . ')');
 
-         $this->xmlWriter->endElement();
 
-     }
 
-     public function done() : string
 
-     {
 
-         if (null === $this->xmlWriter) {
 
-             throw new RuntimeException('No image has been started');
 
-         }
 
-         foreach ($this->stack as $openElements) {
 
-             for ($i = $openElements; $i > 0; --$i) {
 
-                 $this->xmlWriter->endElement();
 
-             }
 
-         }
 
-         $this->xmlWriter->endDocument();
 
-         $blob = $this->xmlWriter->outputMemory(true);
 
-         $this->xmlWriter = null;
 
-         $this->stack = null;
 
-         $this->currentStack = null;
 
-         $this->gradientCount = null;
 
-         return $blob;
 
-     }
 
-     private function startPathElement(Path $path) : void
 
-     {
 
-         $pathData = [];
 
-         foreach ($path as $op) {
 
-             switch (true) {
 
-                 case $op instanceof Move:
 
-                     $pathData[] = sprintf(
 
-                         'M%s %s',
 
-                         round($op->getX(), self::PRECISION),
 
-                         round($op->getY(), self::PRECISION)
 
-                     );
 
-                     break;
 
-                 case $op instanceof Line:
 
-                     $pathData[] = sprintf(
 
-                         'L%s %s',
 
-                         round($op->getX(), self::PRECISION),
 
-                         round($op->getY(), self::PRECISION)
 
-                     );
 
-                     break;
 
-                 case $op instanceof EllipticArc:
 
-                     $pathData[] = sprintf(
 
-                         'A%s %s %s %u %u %s %s',
 
-                         round($op->getXRadius(), self::PRECISION),
 
-                         round($op->getYRadius(), self::PRECISION),
 
-                         round($op->getXAxisAngle(), self::PRECISION),
 
-                         $op->isLargeArc(),
 
-                         $op->isSweep(),
 
-                         round($op->getX(), self::PRECISION),
 
-                         round($op->getY(), self::PRECISION)
 
-                     );
 
-                     break;
 
-                 case $op instanceof Curve:
 
-                     $pathData[] = sprintf(
 
-                         'C%s %s %s %s %s %s',
 
-                         round($op->getX1(), self::PRECISION),
 
-                         round($op->getY1(), self::PRECISION),
 
-                         round($op->getX2(), self::PRECISION),
 
-                         round($op->getY2(), self::PRECISION),
 
-                         round($op->getX3(), self::PRECISION),
 
-                         round($op->getY3(), self::PRECISION)
 
-                     );
 
-                     break;
 
-                 case $op instanceof Close:
 
-                     $pathData[] = 'Z';
 
-                     break;
 
-                 default:
 
-                     throw new RuntimeException('Unexpected draw operation: ' . get_class($op));
 
-             }
 
-         }
 
-         $this->xmlWriter->startElement('path');
 
-         $this->xmlWriter->writeAttribute('fill-rule', 'evenodd');
 
-         $this->xmlWriter->writeAttribute('d', implode('', $pathData));
 
-     }
 
-     private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : string
 
-     {
 
-         $this->xmlWriter->startElement('defs');
 
-         $startColor = $gradient->getStartColor();
 
-         $endColor = $gradient->getEndColor();
 
-         if ($gradient->getType() === GradientType::RADIAL()) {
 
-             $this->xmlWriter->startElement('radialGradient');
 
-         } else {
 
-             $this->xmlWriter->startElement('linearGradient');
 
-         }
 
-         $this->xmlWriter->writeAttribute('gradientUnits', 'userSpaceOnUse');
 
-         switch ($gradient->getType()) {
 
-             case GradientType::HORIZONTAL():
 
-                 $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
 
-                 $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION));
 
-                 $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION));
 
-                 $this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION));
 
-                 break;
 
-             case GradientType::VERTICAL():
 
-                 $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
 
-                 $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION));
 
-                 $this->xmlWriter->writeAttribute('x2', (string) round($x, self::PRECISION));
 
-                 $this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION));
 
-                 break;
 
-             case GradientType::DIAGONAL():
 
-                 $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
 
-                 $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION));
 
-                 $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION));
 
-                 $this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION));
 
-                 break;
 
-             case GradientType::INVERSE_DIAGONAL():
 
-                 $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
 
-                 $this->xmlWriter->writeAttribute('y1', (string) round($y + $height, self::PRECISION));
 
-                 $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION));
 
-                 $this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION));
 
-                 break;
 
-             case GradientType::RADIAL():
 
-                 $this->xmlWriter->writeAttribute('cx', (string) round(($x + $width) / 2, self::PRECISION));
 
-                 $this->xmlWriter->writeAttribute('cy', (string) round(($y + $height) / 2, self::PRECISION));
 
-                 $this->xmlWriter->writeAttribute('r', (string) round(max($width, $height) / 2, self::PRECISION));
 
-                 break;
 
-         }
 
-         $id = sprintf('g%d', ++$this->gradientCount);
 
-         $this->xmlWriter->writeAttribute('id', $id);
 
-         $this->xmlWriter->startElement('stop');
 
-         $this->xmlWriter->writeAttribute('offset', '0%');
 
-         $this->xmlWriter->writeAttribute('stop-color', $this->getColorString($startColor));
 
-         if ($startColor instanceof Alpha) {
 
-             $this->xmlWriter->writeAttribute('stop-opacity', $startColor->getAlpha());
 
-         }
 
-         $this->xmlWriter->endElement();
 
-         $this->xmlWriter->startElement('stop');
 
-         $this->xmlWriter->writeAttribute('offset', '100%');
 
-         $this->xmlWriter->writeAttribute('stop-color', $this->getColorString($endColor));
 
-         if ($endColor instanceof Alpha) {
 
-             $this->xmlWriter->writeAttribute('stop-opacity', $endColor->getAlpha());
 
-         }
 
-         $this->xmlWriter->endElement();
 
-         $this->xmlWriter->endElement();
 
-         $this->xmlWriter->endElement();
 
-         return $id;
 
-     }
 
-     private function getColorString(ColorInterface $color) : string
 
-     {
 
-         $color = $color->toRgb();
 
-         return sprintf(
 
-             '#%02x%02x%02x',
 
-             $color->getRed(),
 
-             $color->getGreen(),
 
-             $color->getBlue()
 
-         );
 
-     }
 
- }
 
 
  |