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

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\QrCodeInterface;
  9. use Endroid\QrCode\Writer\Result\ResultInterface;
  10. use Endroid\QrCode\Writer\Result\SvgResult;
  11. final class SvgWriter implements WriterInterface
  12. {
  13. public const DECIMAL_PRECISION = 10;
  14. public const WRITER_OPTION_BLOCK_ID = 'block_id';
  15. public const WRITER_OPTION_EXCLUDE_XML_DECLARATION = 'exclude_xml_declaration';
  16. public const WRITER_OPTION_EXCLUDE_SVG_WIDTH_AND_HEIGHT = 'exclude_svg_width_and_height';
  17. public const WRITER_OPTION_FORCE_XLINK_HREF = 'force_xlink_href';
  18. public function write(QrCodeInterface $qrCode, LogoInterface $logo = null, LabelInterface $label = null, array $options = []): ResultInterface
  19. {
  20. if (!isset($options[self::WRITER_OPTION_BLOCK_ID])) {
  21. $options[self::WRITER_OPTION_BLOCK_ID] = 'block';
  22. }
  23. if (!isset($options[self::WRITER_OPTION_EXCLUDE_XML_DECLARATION])) {
  24. $options[self::WRITER_OPTION_EXCLUDE_XML_DECLARATION] = false;
  25. }
  26. if (!isset($options[self::WRITER_OPTION_EXCLUDE_SVG_WIDTH_AND_HEIGHT])) {
  27. $options[self::WRITER_OPTION_EXCLUDE_SVG_WIDTH_AND_HEIGHT] = false;
  28. }
  29. $matrixFactory = new MatrixFactory();
  30. $matrix = $matrixFactory->create($qrCode);
  31. $xml = new \SimpleXMLElement('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"/>');
  32. $xml->addAttribute('version', '1.1');
  33. if (!$options[self::WRITER_OPTION_EXCLUDE_SVG_WIDTH_AND_HEIGHT]) {
  34. $xml->addAttribute('width', $matrix->getOuterSize().'px');
  35. $xml->addAttribute('height', $matrix->getOuterSize().'px');
  36. }
  37. $xml->addAttribute('viewBox', '0 0 '.$matrix->getOuterSize().' '.$matrix->getOuterSize());
  38. $xml->addChild('defs');
  39. $blockDefinition = $xml->defs->addChild('rect');
  40. $blockDefinition->addAttribute('id', strval($options[self::WRITER_OPTION_BLOCK_ID]));
  41. $blockDefinition->addAttribute('width', number_format($matrix->getBlockSize(), self::DECIMAL_PRECISION, '.', ''));
  42. $blockDefinition->addAttribute('height', number_format($matrix->getBlockSize(), self::DECIMAL_PRECISION, '.', ''));
  43. $blockDefinition->addAttribute('fill', '#'.sprintf('%02x%02x%02x', $qrCode->getForegroundColor()->getRed(), $qrCode->getForegroundColor()->getGreen(), $qrCode->getForegroundColor()->getBlue()));
  44. $blockDefinition->addAttribute('fill-opacity', strval($qrCode->getForegroundColor()->getOpacity()));
  45. $background = $xml->addChild('rect');
  46. $background->addAttribute('x', '0');
  47. $background->addAttribute('y', '0');
  48. $background->addAttribute('width', strval($matrix->getOuterSize()));
  49. $background->addAttribute('height', strval($matrix->getOuterSize()));
  50. $background->addAttribute('fill', '#'.sprintf('%02x%02x%02x', $qrCode->getBackgroundColor()->getRed(), $qrCode->getBackgroundColor()->getGreen(), $qrCode->getBackgroundColor()->getBlue()));
  51. $background->addAttribute('fill-opacity', strval($qrCode->getBackgroundColor()->getOpacity()));
  52. for ($rowIndex = 0; $rowIndex < $matrix->getBlockCount(); ++$rowIndex) {
  53. for ($columnIndex = 0; $columnIndex < $matrix->getBlockCount(); ++$columnIndex) {
  54. if (1 === $matrix->getBlockValue($rowIndex, $columnIndex)) {
  55. $block = $xml->addChild('use');
  56. $block->addAttribute('x', number_format($matrix->getMarginLeft() + $matrix->getBlockSize() * $columnIndex, self::DECIMAL_PRECISION, '.', ''));
  57. $block->addAttribute('y', number_format($matrix->getMarginLeft() + $matrix->getBlockSize() * $rowIndex, self::DECIMAL_PRECISION, '.', ''));
  58. $block->addAttribute('xlink:href', '#'.$options[self::WRITER_OPTION_BLOCK_ID], 'http://www.w3.org/1999/xlink');
  59. }
  60. }
  61. }
  62. $result = new SvgResult($matrix, $xml, boolval($options[self::WRITER_OPTION_EXCLUDE_XML_DECLARATION]));
  63. if ($logo instanceof LogoInterface) {
  64. $this->addLogo($logo, $result, $options);
  65. }
  66. return $result;
  67. }
  68. /** @param array<string, mixed> $options */
  69. private function addLogo(LogoInterface $logo, SvgResult $result, array $options): void
  70. {
  71. $logoImageData = LogoImageData::createForLogo($logo);
  72. if (!isset($options[self::WRITER_OPTION_FORCE_XLINK_HREF])) {
  73. $options[self::WRITER_OPTION_FORCE_XLINK_HREF] = false;
  74. }
  75. $xml = $result->getXml();
  76. /** @var \SimpleXMLElement $xmlAttributes */
  77. $xmlAttributes = $xml->attributes();
  78. $x = intval($xmlAttributes->width) / 2 - $logoImageData->getWidth() / 2;
  79. $y = intval($xmlAttributes->height) / 2 - $logoImageData->getHeight() / 2;
  80. $imageDefinition = $xml->addChild('image');
  81. $imageDefinition->addAttribute('x', strval($x));
  82. $imageDefinition->addAttribute('y', strval($y));
  83. $imageDefinition->addAttribute('width', strval($logoImageData->getWidth()));
  84. $imageDefinition->addAttribute('height', strval($logoImageData->getHeight()));
  85. $imageDefinition->addAttribute('preserveAspectRatio', 'none');
  86. if ($options[self::WRITER_OPTION_FORCE_XLINK_HREF]) {
  87. $imageDefinition->addAttribute('xlink:href', $logoImageData->createDataUri(), 'http://www.w3.org/1999/xlink');
  88. } else {
  89. $imageDefinition->addAttribute('href', $logoImageData->createDataUri());
  90. }
  91. }
  92. }