svgWithLogo.php 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. <?php
  2. /**
  3. * @created 05.03.2022
  4. * @author smiley <smiley@chillerlan.net>
  5. * @copyright 2022 smiley
  6. * @license MIT
  7. *
  8. * @noinspection PhpComposerExtensionStubsInspection
  9. */
  10. use chillerlan\QRCode\{QRCode, QRCodeException, QROptions};
  11. use chillerlan\QRCode\Common\EccLevel;
  12. use chillerlan\QRCode\Data\QRMatrix;
  13. use chillerlan\QRCode\Output\{QROutputInterface, QRMarkupSVG};
  14. require_once __DIR__.'/../vendor/autoload.php';
  15. /*
  16. * Class definition
  17. */
  18. /**
  19. * Create SVG QR Codes with embedded logos (that are also SVG)
  20. */
  21. class QRSvgWithLogo extends QRMarkupSVG{
  22. /**
  23. * @inheritDoc
  24. */
  25. protected function paths():string{
  26. $size = (int)ceil($this->moduleCount * $this->options->svgLogoScale);
  27. // we're calling QRMatrix::setLogoSpace() manually, so QROptions::$addLogoSpace has no effect here
  28. $this->matrix->setLogoSpace($size, $size);
  29. $svg = parent::paths();
  30. $svg .= $this->getLogo();
  31. return $svg;
  32. }
  33. /**
  34. * returns a <g> element that contains the SVG logo and positions it properly within the QR Code
  35. *
  36. * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
  37. * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform
  38. */
  39. protected function getLogo():string{
  40. // @todo: customize the <g> element to your liking (css class, style...)
  41. return sprintf(
  42. '%5$s<g transform="translate(%1$s %1$s) scale(%2$s)" class="%3$s">%5$s %4$s%5$s</g>',
  43. ($this->moduleCount - ($this->moduleCount * $this->options->svgLogoScale)) / 2,
  44. $this->options->svgLogoScale,
  45. $this->options->svgLogoCssClass,
  46. file_get_contents($this->options->svgLogo),
  47. $this->options->eol
  48. );
  49. }
  50. }
  51. /**
  52. * augment the QROptions class
  53. */
  54. class SVGWithLogoOptions extends QROptions{
  55. // path to svg logo
  56. protected string $svgLogo;
  57. // logo scale in % of QR Code size, clamped to 10%-30%
  58. protected float $svgLogoScale = 0.20;
  59. // css class for the logo (defined in $svgDefs)
  60. protected string $svgLogoCssClass = '';
  61. // check logo
  62. protected function set_svgLogo(string $svgLogo):void{
  63. if(!file_exists($svgLogo) || !is_readable($svgLogo)){
  64. throw new QRCodeException('invalid svg logo');
  65. }
  66. // @todo: validate svg
  67. $this->svgLogo = $svgLogo;
  68. }
  69. // clamp logo scale
  70. protected function set_svgLogoScale(float $svgLogoScale):void{
  71. $this->svgLogoScale = max(0.05, min(0.3, $svgLogoScale));
  72. }
  73. }
  74. /*
  75. * Runtime
  76. */
  77. $options = new SVGWithLogoOptions([
  78. // SVG logo options (see extended class below)
  79. 'svgLogo' => __DIR__.'/github.svg', // logo from: https://github.com/simple-icons/simple-icons
  80. 'svgLogoScale' => 0.25,
  81. 'svgLogoCssClass' => 'dark',
  82. // QROptions
  83. 'version' => 5,
  84. 'outputType' => QROutputInterface::CUSTOM,
  85. 'outputInterface' => QRSvgWithLogo::class,
  86. 'imageBase64' => false,
  87. // ECC level H is necessary when using logos
  88. 'eccLevel' => EccLevel::H,
  89. 'addQuietzone' => true,
  90. // if set to true, the light modules won't be rendered
  91. 'drawLightModules' => true,
  92. // empty the default value to remove the fill* attributes from the <path> elements
  93. 'markupDark' => '',
  94. 'markupLight' => '',
  95. // draw the modules as circles isntead of squares
  96. 'drawCircularModules' => true,
  97. 'circleRadius' => 0.45,
  98. // connect paths
  99. 'svgConnectPaths' => true,
  100. // keep modules of thhese types as square
  101. 'keepAsSquare' => [
  102. QRMatrix::M_FINDER|QRMatrix::IS_DARK,
  103. QRMatrix::M_FINDER_DOT,
  104. QRMatrix::M_ALIGNMENT|QRMatrix::IS_DARK,
  105. ],
  106. // https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient
  107. 'svgDefs' => '
  108. <linearGradient id="gradient" x1="100%" y2="100%">
  109. <stop stop-color="#D70071" offset="0"/>
  110. <stop stop-color="#9C4E97" offset="0.5"/>
  111. <stop stop-color="#0035A9" offset="1"/>
  112. </linearGradient>
  113. <style><![CDATA[
  114. .dark{fill: url(#gradient);}
  115. .light{fill: #eaeaea;}
  116. ]]></style>',
  117. ]);
  118. $qrcode = (new QRCode($options))->render('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
  119. if(php_sapi_name() !== 'cli'){
  120. header('Content-type: image/svg+xml');
  121. if(extension_loaded('zlib')){
  122. header('Vary: Accept-Encoding');
  123. header('Content-Encoding: gzip');
  124. $qrcode = gzencode($qrcode, 9);
  125. }
  126. }
  127. echo $qrcode;
  128. exit;