vendor/endroid/qr-code/src/Writer/SvgWriter.php line 25

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Endroid\QrCode\Writer;
  4. use Endroid\QrCode\Bacon\MatrixFactory;
  5. use Endroid\QrCode\ImageData\LogoImageData;
  6. use Endroid\QrCode\Label\LabelInterface;
  7. use Endroid\QrCode\Logo\LogoInterface;
  8. use Endroid\QrCode\Matrix\MatrixInterface;
  9. use Endroid\QrCode\QrCodeInterface;
  10. use Endroid\QrCode\Writer\Result\ResultInterface;
  11. use Endroid\QrCode\Writer\Result\SvgResult;
  12. final class SvgWriter implements WriterInterface
  13. {
  14.     public const DECIMAL_PRECISION 2;
  15.     public const WRITER_OPTION_COMPACT 'compact';
  16.     public const WRITER_OPTION_BLOCK_ID 'block_id';
  17.     public const WRITER_OPTION_EXCLUDE_XML_DECLARATION 'exclude_xml_declaration';
  18.     public const WRITER_OPTION_EXCLUDE_SVG_WIDTH_AND_HEIGHT 'exclude_svg_width_and_height';
  19.     public const WRITER_OPTION_FORCE_XLINK_HREF 'force_xlink_href';
  20.     public function write(QrCodeInterface $qrCodeLogoInterface $logo nullLabelInterface $label null, array $options = []): ResultInterface
  21.     {
  22.         if (!isset($options[self::WRITER_OPTION_COMPACT])) {
  23.             $options[self::WRITER_OPTION_COMPACT] = true;
  24.         }
  25.         if (!isset($options[self::WRITER_OPTION_BLOCK_ID])) {
  26.             $options[self::WRITER_OPTION_BLOCK_ID] = 'block';
  27.         }
  28.         if (!isset($options[self::WRITER_OPTION_EXCLUDE_XML_DECLARATION])) {
  29.             $options[self::WRITER_OPTION_EXCLUDE_XML_DECLARATION] = false;
  30.         }
  31.         if (!isset($options[self::WRITER_OPTION_EXCLUDE_SVG_WIDTH_AND_HEIGHT])) {
  32.             $options[self::WRITER_OPTION_EXCLUDE_SVG_WIDTH_AND_HEIGHT] = false;
  33.         }
  34.         $matrixFactory = new MatrixFactory();
  35.         $matrix $matrixFactory->create($qrCode);
  36.         $xml = new \SimpleXMLElement('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"/>');
  37.         $xml->addAttribute('version''1.1');
  38.         if (!$options[self::WRITER_OPTION_EXCLUDE_SVG_WIDTH_AND_HEIGHT]) {
  39.             $xml->addAttribute('width'$matrix->getOuterSize().'px');
  40.             $xml->addAttribute('height'$matrix->getOuterSize().'px');
  41.         }
  42.         $xml->addAttribute('viewBox''0 0 '.$matrix->getOuterSize().' '.$matrix->getOuterSize());
  43.         $background $xml->addChild('rect');
  44.         $background->addAttribute('x''0');
  45.         $background->addAttribute('y''0');
  46.         $background->addAttribute('width'strval($matrix->getOuterSize()));
  47.         $background->addAttribute('height'strval($matrix->getOuterSize()));
  48.         $background->addAttribute('fill''#'.sprintf('%02x%02x%02x'$qrCode->getBackgroundColor()->getRed(), $qrCode->getBackgroundColor()->getGreen(), $qrCode->getBackgroundColor()->getBlue()));
  49.         $background->addAttribute('fill-opacity'strval($qrCode->getBackgroundColor()->getOpacity()));
  50.         if ($options[self::WRITER_OPTION_COMPACT]) {
  51.             $this->writePath($xml$qrCode$matrix);
  52.         } else {
  53.             $this->writeBlockDefinitions($xml$qrCode$matrix$options);
  54.         }
  55.         $result = new SvgResult($matrix$xmlboolval($options[self::WRITER_OPTION_EXCLUDE_XML_DECLARATION]));
  56.         if ($logo instanceof LogoInterface) {
  57.             $this->addLogo($logo$result$options);
  58.         }
  59.         return $result;
  60.     }
  61.     private function writePath(\SimpleXMLElement $xmlQrCodeInterface $qrCodeMatrixInterface $matrix): void
  62.     {
  63.         $path '';
  64.         for ($rowIndex 0$rowIndex $matrix->getBlockCount(); ++$rowIndex) {
  65.             $left $matrix->getMarginLeft();
  66.             for ($columnIndex 0$columnIndex $matrix->getBlockCount(); ++$columnIndex) {
  67.                 if (=== $matrix->getBlockValue($rowIndex$columnIndex)) {
  68.                     // When we are at the first column or when the previous column was 0 set new left
  69.                     if (=== $columnIndex || === $matrix->getBlockValue($rowIndex$columnIndex 1)) {
  70.                         $left $matrix->getMarginLeft() + $matrix->getBlockSize() * $columnIndex;
  71.                     }
  72.                     // When we are at the
  73.                     if ($columnIndex === $matrix->getBlockCount() - || === $matrix->getBlockValue($rowIndex$columnIndex 1)) {
  74.                         $top $matrix->getMarginLeft() + $matrix->getBlockSize() * $rowIndex;
  75.                         $bottom $matrix->getMarginLeft() + $matrix->getBlockSize() * ($rowIndex 1);
  76.                         $right $matrix->getMarginLeft() + $matrix->getBlockSize() * ($columnIndex 1);
  77.                         $path .= 'M'.$this->formatNumber($left).','.$this->formatNumber($top);
  78.                         $path .= 'L'.$this->formatNumber($right).','.$this->formatNumber($top);
  79.                         $path .= 'L'.$this->formatNumber($right).','.$this->formatNumber($bottom);
  80.                         $path .= 'L'.$this->formatNumber($left).','.$this->formatNumber($bottom).'Z';
  81.                     }
  82.                 }
  83.             }
  84.         }
  85.         $pathDefinition $xml->addChild('path');
  86.         $pathDefinition->addAttribute('fill''#'.sprintf('%02x%02x%02x'$qrCode->getForegroundColor()->getRed(), $qrCode->getForegroundColor()->getGreen(), $qrCode->getForegroundColor()->getBlue()));
  87.         $pathDefinition->addAttribute('fill-opacity'strval($qrCode->getForegroundColor()->getOpacity()));
  88.         $pathDefinition->addAttribute('d'$path);
  89.     }
  90.     /** @param array<string, mixed> $options */
  91.     private function writeBlockDefinitions(\SimpleXMLElement $xmlQrCodeInterface $qrCodeMatrixInterface $matrix, array $options): void
  92.     {
  93.         $xml->addChild('defs');
  94.         $blockDefinition $xml->defs->addChild('rect');
  95.         $blockDefinition->addAttribute('id'strval($options[self::WRITER_OPTION_BLOCK_ID]));
  96.         $blockDefinition->addAttribute('width'$this->formatNumber($matrix->getBlockSize()));
  97.         $blockDefinition->addAttribute('height'$this->formatNumber($matrix->getBlockSize()));
  98.         $blockDefinition->addAttribute('fill''#'.sprintf('%02x%02x%02x'$qrCode->getForegroundColor()->getRed(), $qrCode->getForegroundColor()->getGreen(), $qrCode->getForegroundColor()->getBlue()));
  99.         $blockDefinition->addAttribute('fill-opacity'strval($qrCode->getForegroundColor()->getOpacity()));
  100.         for ($rowIndex 0$rowIndex $matrix->getBlockCount(); ++$rowIndex) {
  101.             for ($columnIndex 0$columnIndex $matrix->getBlockCount(); ++$columnIndex) {
  102.                 if (=== $matrix->getBlockValue($rowIndex$columnIndex)) {
  103.                     $block $xml->addChild('use');
  104.                     $block->addAttribute('x'$this->formatNumber($matrix->getMarginLeft() + $matrix->getBlockSize() * $columnIndex));
  105.                     $block->addAttribute('y'$this->formatNumber($matrix->getMarginLeft() + $matrix->getBlockSize() * $rowIndex));
  106.                     $block->addAttribute('xlink:href''#'.$options[self::WRITER_OPTION_BLOCK_ID], 'http://www.w3.org/1999/xlink');
  107.                 }
  108.             }
  109.         }
  110.     }
  111.     /** @param array<string, mixed> $options */
  112.     private function addLogo(LogoInterface $logoSvgResult $result, array $options): void
  113.     {
  114.         $logoImageData LogoImageData::createForLogo($logo);
  115.         if (!isset($options[self::WRITER_OPTION_FORCE_XLINK_HREF])) {
  116.             $options[self::WRITER_OPTION_FORCE_XLINK_HREF] = false;
  117.         }
  118.         $xml $result->getXml();
  119.         /** @var \SimpleXMLElement $xmlAttributes */
  120.         $xmlAttributes $xml->attributes();
  121.         $x intval($xmlAttributes->width) / $logoImageData->getWidth() / 2;
  122.         $y intval($xmlAttributes->height) / $logoImageData->getHeight() / 2;
  123.         $imageDefinition $xml->addChild('image');
  124.         $imageDefinition->addAttribute('x'strval($x));
  125.         $imageDefinition->addAttribute('y'strval($y));
  126.         $imageDefinition->addAttribute('width'strval($logoImageData->getWidth()));
  127.         $imageDefinition->addAttribute('height'strval($logoImageData->getHeight()));
  128.         $imageDefinition->addAttribute('preserveAspectRatio''none');
  129.         if ($options[self::WRITER_OPTION_FORCE_XLINK_HREF]) {
  130.             $imageDefinition->addAttribute('xlink:href'$logoImageData->createDataUri(), 'http://www.w3.org/1999/xlink');
  131.         } else {
  132.             $imageDefinition->addAttribute('href'$logoImageData->createDataUri());
  133.         }
  134.     }
  135.     private function formatNumber(float $number): string
  136.     {
  137.         $string number_format($numberself::DECIMAL_PRECISION'.''');
  138.         $string rtrim($string'0');
  139.         return rtrim($string'.');
  140.     }
  141. }