QRMarkupSVG.php 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. <?php
  2. /**
  3. * Class QRMarkupSVG
  4. *
  5. * @created 06.06.2022
  6. * @author smiley <smiley@chillerlan.net>
  7. * @copyright 2022 smiley
  8. * @license MIT
  9. */
  10. namespace chillerlan\QRCode\Output;
  11. use chillerlan\QRCode\Data\QRMatrix;
  12. use function array_chunk, implode, sprintf;
  13. /**
  14. * SVG output
  15. *
  16. * @see https://github.com/codemasher/php-qrcode/pull/5
  17. * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg
  18. * @see https://www.sarasoueidan.com/demos/interactive-svg-coordinate-system/
  19. * @see http://apex.infogridpacific.com/SVG/svg-tutorial-contents.html
  20. */
  21. class QRMarkupSVG extends QRMarkup{
  22. /**
  23. * @inheritDoc
  24. */
  25. protected function createMarkup(bool $saveToFile):string{
  26. $svg = $this->header();
  27. if(!empty($this->options->svgDefs)){
  28. $svg .= sprintf('<defs>%1$s%2$s</defs>%2$s', $this->options->svgDefs, $this->options->eol);
  29. }
  30. $svg .= $this->paths();
  31. // close svg
  32. $svg .= sprintf('%1$s</svg>%1$s', $this->options->eol);
  33. // transform to data URI only when not saving to file
  34. if(!$saveToFile && $this->options->imageBase64){
  35. $svg = $this->toBase64DataURI($svg, 'image/svg+xml');
  36. }
  37. return $svg;
  38. }
  39. /**
  40. * returns the <svg> header with the given options parsed
  41. */
  42. protected function header():string{
  43. $width = $this->options->svgWidth !== null ? sprintf(' width="%s"', $this->options->svgWidth) : '';
  44. $height = $this->options->svgHeight !== null ? sprintf(' height="%s"', $this->options->svgHeight) : '';
  45. /** @noinspection HtmlUnknownAttribute */
  46. return sprintf(
  47. '<?xml version="1.0" encoding="UTF-8"?>%6$s'.
  48. '<svg xmlns="http://www.w3.org/2000/svg" class="qr-svg %1$s" viewBox="0 0 %2$s %2$s" preserveAspectRatio="%3$s"%4$s%5$s>%6$s',
  49. $this->options->cssClass,
  50. $this->options->svgViewBoxSize ?? $this->moduleCount,
  51. $this->options->svgPreserveAspectRatio,
  52. $width,
  53. $height,
  54. $this->options->eol
  55. );
  56. }
  57. /**
  58. * returns one or more SVG <path> elements
  59. */
  60. protected function paths():string{
  61. $paths = $this->collectModules(fn(int $x, int $y, int $M_TYPE):string => $this->module($x, $y, $M_TYPE));
  62. $svg = [];
  63. // create the path elements
  64. foreach($paths as $M_TYPE => $modules){
  65. // limit the total line length
  66. $chunks = array_chunk($modules, 100);
  67. $chonks = [];
  68. foreach($chunks as $chunk){
  69. $chonks[] = implode(' ', $chunk);
  70. }
  71. $path = implode($this->options->eol, $chonks);
  72. if(empty($path)){
  73. continue;
  74. }
  75. $svg[] = $this->path($path, $M_TYPE);
  76. }
  77. return implode($this->options->eol, $svg);
  78. }
  79. /**
  80. * renders and returns a single <path> element
  81. *
  82. * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
  83. */
  84. protected function path(string $path, int $M_TYPE):string{
  85. // ignore non-existent module values
  86. $format = !isset($this->moduleValues[$M_TYPE]) || empty($this->moduleValues[$M_TYPE])
  87. ? '<path class="%1$s" d="%2$s"/>'
  88. : '<path class="%1$s" fill="%3$s" fill-opacity="%4$s" d="%2$s"/>';
  89. return sprintf(
  90. $format,
  91. $this->getCssClass($M_TYPE),
  92. $path,
  93. $this->moduleValues[$M_TYPE] ?? '', // value may or may not exist
  94. $this->options->svgOpacity
  95. );
  96. }
  97. /**
  98. * @inheritDoc
  99. */
  100. protected function getCssClass(int $M_TYPE):string{
  101. return implode(' ', [
  102. 'qr-'.$M_TYPE,
  103. ($M_TYPE & QRMatrix::IS_DARK) === QRMatrix::IS_DARK ? 'dark' : 'light',
  104. $this->options->cssClass,
  105. ]);
  106. }
  107. /**
  108. * returns a path segment for a single module
  109. *
  110. * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
  111. */
  112. protected function module(int $x, int $y, int $M_TYPE):string{
  113. if(!$this->options->drawLightModules && !$this->matrix->check($x, $y)){
  114. return '';
  115. }
  116. if($this->options->drawCircularModules && $this->matrix->checkTypeNotIn($x, $y, $this->options->keepAsSquare)){
  117. $r = $this->options->circleRadius;
  118. return sprintf(
  119. 'M%1$s %2$s a%3$s %3$s 0 1 0 %4$s 0 a%3$s %3$s 0 1 0 -%4$s 0Z',
  120. ($x + 0.5 - $r),
  121. ($y + 0.5),
  122. $r,
  123. ($r * 2)
  124. );
  125. }
  126. return sprintf('M%1$s %2$s h1 v1 h-1Z', $x, $y);
  127. }
  128. }