Skip to content

Commit 40604f4

Browse files
committed
✨ SVG QR Code with logo example
1 parent e8ac9e0 commit 40604f4

File tree

3 files changed

+176
-0
lines changed

3 files changed

+176
-0
lines changed

examples/QRSvgWithLogo.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
/**
3+
* Class QRSvgWithLogo
4+
*
5+
* @created 05.03.2022
6+
* @author smiley <[email protected]>
7+
* @copyright 2022 smiley
8+
* @license MIT
9+
*/
10+
11+
namespace chillerlan\QRCodeExamples;
12+
13+
use chillerlan\QRCode\Output\QRMarkup;
14+
use function file_get_contents, sprintf;
15+
16+
/**
17+
* Create SVG QR Codes with embedded logos (that are also SVG)
18+
*/
19+
class QRSvgWithLogo extends QRMarkup{
20+
21+
/**
22+
* SVG output
23+
*
24+
* @see https://github.com/codemasher/php-qrcode/pull/5
25+
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg
26+
* @see https://www.sarasoueidan.com/demos/interactive-svg-coordinate-system/
27+
*/
28+
protected function svg(bool $saveToFile):string{
29+
$size = (int)ceil($this->moduleCount * $this->options->svgLogoScale);
30+
31+
// we're calling QRMatrix::setLogoSpace() manually, so QROptions::$addLogoSpace has no effect here
32+
$this->matrix->setLogoSpace($size, $size);
33+
34+
$svg = $this->svgHeader();
35+
36+
if(!empty($this->options->svgDefs)){
37+
$svg .= sprintf('<defs>%1$s%2$s</defs>%2$s', $this->options->svgDefs, $this->options->eol);
38+
}
39+
40+
$svg .= $this->svgPaths();
41+
$svg .= $this->getLogo();
42+
43+
// close svg
44+
$svg .= sprintf('%1$s</svg>%1$s', $this->options->eol);
45+
46+
// transform to data URI only when not saving to file
47+
if(!$saveToFile && $this->options->imageBase64){
48+
$svg = $this->base64encode($svg, 'image/svg+xml');
49+
}
50+
51+
return $svg;
52+
}
53+
54+
/**
55+
* returns a <g> element that contains the SVG logo and positions it properly within the QR Code
56+
*
57+
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
58+
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform
59+
*/
60+
protected function getLogo():string{
61+
// @todo: customize the <g> element to your liking (css class, style...)
62+
return sprintf(
63+
'%5$s<g transform="translate(%1$s %1$s) scale(%2$s)" class="%3$s">%5$s %4$s%5$s</g>',
64+
($this->moduleCount - ($this->moduleCount * $this->options->svgLogoScale)) / 2,
65+
$this->options->svgLogoScale,
66+
$this->options->svgLogoCssClass,
67+
file_get_contents($this->options->svgLogo),
68+
$this->options->eol
69+
);
70+
}
71+
72+
}

examples/github.svg

Lines changed: 1 addition & 0 deletions
Loading

examples/svgWithLogo.php

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
/**
3+
* @created 05.03.2022
4+
* @author smiley <[email protected]>
5+
* @copyright 2022 smiley
6+
* @license MIT
7+
*
8+
* @noinspection PhpComposerExtensionStubsInspection
9+
*/
10+
11+
namespace chillerlan\QRCodeExamples;
12+
13+
use chillerlan\QRCode\{QRCode, QRCodeException, QROptions};
14+
use chillerlan\QRCode\Data\QRMatrix;
15+
use chillerlan\QRCode\Common\EccLevel;
16+
use function file_exists, gzencode, header, is_readable, max, min;
17+
18+
require_once __DIR__.'/../vendor/autoload.php';
19+
20+
$data = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ';
21+
$gzip = true;
22+
23+
$options_arr = [
24+
// SVG logo options (see extended class below)
25+
'svgLogo' => __DIR__.'/github.svg', // logo from: https://github.com/simple-icons/simple-icons
26+
'svgLogoScale' => 0.25,
27+
'svgLogoCssClass' => 'dark',
28+
// QROptions
29+
'version' => 5,
30+
'outputType' => QRCode::OUTPUT_CUSTOM,
31+
'outputInterface' => QRSvgWithLogo::class,
32+
'imageBase64' => false,
33+
// ECC level H is necessary when using logos
34+
'eccLevel' => EccLevel::H,
35+
'addQuietzone' => true,
36+
// if set to true, the light modules won't be rendered
37+
'imageTransparent' => false,
38+
// empty the default value to remove the fill* attributes from the <path> elements
39+
'markupDark' => '',
40+
'markupLight' => '',
41+
// draw the modules as circles isntead of squares
42+
'drawCircularModules' => true,
43+
'circleRadius' => 0.45,
44+
// connect paths
45+
'svgConnectPaths' => true,
46+
// keep modules of thhese types as square
47+
'keepAsSquare' => [
48+
QRMatrix::M_FINDER|QRMatrix::IS_DARK,
49+
QRMatrix::M_FINDER_DOT,
50+
QRMatrix::M_ALIGNMENT|QRMatrix::IS_DARK,
51+
],
52+
// https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient
53+
'svgDefs' => '
54+
<linearGradient id="gradient" x1="100%" y2="100%">
55+
<stop stop-color="#D70071" offset="0"/>
56+
<stop stop-color="#9C4E97" offset="0.5"/>
57+
<stop stop-color="#0035A9" offset="1"/>
58+
</linearGradient>
59+
<style><![CDATA[
60+
.dark{fill: url(#gradient);}
61+
.light{fill: #eaeaea;}
62+
]]></style>',
63+
];
64+
65+
// augment the QROptions class
66+
$options = new class ($options_arr) extends QROptions{
67+
// path to svg logo
68+
protected string $svgLogo;
69+
// logo scale in % of QR Code size, clamped to 10%-30%
70+
protected float $svgLogoScale = 0.20;
71+
// css class for the logo (defined in $svgDefs)
72+
protected string $svgLogoCssClass = '';
73+
74+
// check logo
75+
protected function set_svgLogo(string $svgLogo):void{
76+
77+
if(!file_exists($svgLogo) || !is_readable($svgLogo)){
78+
throw new QRCodeException('invalid svg logo');
79+
}
80+
81+
// @todo: validate svg
82+
83+
$this->svgLogo = $svgLogo;
84+
}
85+
86+
// clamp logo scale
87+
protected function set_svgLogoScale(float $svgLogoScale):void{
88+
$this->svgLogoScale = max(0.05, min(0.3, $svgLogoScale));
89+
}
90+
91+
};
92+
93+
$qrcode = (new QRCode($options))->render($data);
94+
95+
header('Content-type: image/svg+xml');
96+
97+
if($gzip){
98+
header('Vary: Accept-Encoding');
99+
header('Content-Encoding: gzip');
100+
$qrcode = gzencode($qrcode, 9);
101+
}
102+
103+
echo $qrcode;

0 commit comments

Comments
 (0)