-
-
Notifications
You must be signed in to change notification settings - Fork 302
/
svgWithLogoAndCustomShapes.php
211 lines (171 loc) · 6.01 KB
/
svgWithLogoAndCustomShapes.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
<?php
/**
* SVG with logo and custom shapes example
*
* @see https://github.com/chillerlan/php-qrcode/discussions/150
*
* @created 05.03.2022
* @author smiley <[email protected]>
* @copyright 2022 smiley
* @license MIT
*
* @noinspection PhpComposerExtensionStubsInspection
*/
use chillerlan\QRCode\{QRCode, QRCodeException, QROptions};
use chillerlan\QRCode\Common\EccLevel;
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\QRCode\Output\{QROutputInterface, QRMarkupSVG};
require_once __DIR__.'/../vendor/autoload.php';
/*
* Class definition
*/
/**
* Create SVG QR Codes with embedded logos (that are also SVG)
*/
class QRSvgWithLogoAndCustomShapes extends QRMarkupSVG{
/**
* @inheritDoc
*/
protected function paths():string{
// make sure connect paths is enabled
$this->options->connectPaths = true;
// we're calling QRMatrix::setLogoSpace() manually, so QROptions::$addLogoSpace has no effect here
$this->matrix->setLogoSpace((int)ceil($this->moduleCount * $this->options->svgLogoScale));
// generate the path element(s) - in this case it's just one element as we've "disabled" several options
$svg = parent::paths();
// add the custom shapes for the finder patterns
$svg .= $this->getFinderPatterns();
// and add the custom logo
$svg .= $this->getLogo();
return $svg;
}
/**
* @inheritDoc
*/
protected function path(string $path, int $M_TYPE):string{
// omit the "fill" and "opacity" attributes on the path element
return sprintf('<path class="%s" d="%s"/>', $this->getCssClass($M_TYPE), $path);
}
/**
* returns a path segment for a single module
*
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
*/
protected function module(int $x, int $y, int $M_TYPE):string{
if(
!$this->matrix->isDark($M_TYPE)
// we're skipping the finder patterns here
|| $this->matrix->checkType($x, $y, QRMatrix::M_FINDER)
|| $this->matrix->checkType($x, $y, QRMatrix::M_FINDER_DOT)
){
return '';
}
// return a heart shape (or any custom shape for that matter)
return sprintf('M%1$s %2$s m0.5,0.96 l-0.412,-0.412 a0.3 0.3 0 0 1 0.412,-0.435 a0.3 0.3 0 0 1 0.412,0.435Z', $x, $y);
}
/**
* returns a custom path for the 3 finder patterns
*/
protected function getFinderPatterns():string{
$qz = ($this->options->addQuietzone) ? $this->options->quietzoneSize : 0;
// the positions for the finder patterns (top left corner)
// $this->moduleCount includes 2* the quiet zone size already, so we need to take this into account
$pos = [
[(0 + $qz), (0 + $qz)],
[(0 + $qz), ($this->moduleCount - $qz - 7)],
[($this->moduleCount - $qz - 7), (0 + $qz)],
];
// the custom path for one finder pattern - the first move (M) is parametrized, the rest are relative coordinates
$path = 'M%1$s,%2$s m2,0 h3 q2,0 2,2 v3 q0,2 -2,2 h-3 q-2,0 -2,-2 v-3 q0,-2 2,-2z m0,1 q-1,0 -1,1 v3 '.
'q0,1 1,1 h3 q1,0 1,-1 v-3 q0,-1 -1,-1z m0,2.5 a1.5,1.5 0 1 0 3,0 a1.5,1.5 0 1 0 -3,0Z';
$finder = [];
foreach($pos as [$ix, $iy]){
$finder[] = sprintf($path, $ix, $iy);
}
return sprintf(
'%s<path class="%s" d="%s"/>',
$this->options->eol,
$this->getCssClass(QRMatrix::M_FINDER_DARK),
implode(' ', $finder)
);
}
/**
* returns a <g> element that contains the SVG logo and positions it properly within the QR Code
*
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform
*/
protected function getLogo():string{
// @todo: customize the <g> element to your liking (css class, style...)
return sprintf(
'%5$s<g transform="translate(%1$s %1$s) scale(%2$s)" class="%3$s">%5$s %4$s%5$s</g>',
(($this->moduleCount - ($this->moduleCount * $this->options->svgLogoScale)) / 2),
$this->options->svgLogoScale,
$this->options->svgLogoCssClass,
file_get_contents($this->options->svgLogo),
$this->options->eol
);
}
}
/**
* augment the QROptions class
*/
class SVGWithLogoAndCustomShapesOptions extends QROptions{
// path to svg logo
protected string $svgLogo;
// logo scale in % of QR Code size, clamped to 10%-30%
protected float $svgLogoScale = 0.20;
// css class for the logo (defined in $svgDefs)
protected string $svgLogoCssClass = '';
// check logo
protected function set_svgLogo(string $svgLogo):void{
if(!file_exists($svgLogo) || !is_readable($svgLogo)){
throw new QRCodeException('invalid svg logo');
}
// @todo: validate svg
$this->svgLogo = $svgLogo;
}
// clamp logo scale
protected function set_svgLogoScale(float $svgLogoScale):void{
$this->svgLogoScale = max(0.05, min(0.3, $svgLogoScale));
}
}
/*
* Runtime
*/
// please excuse the IDE yelling https://youtrack.jetbrains.com/issue/WI-66549
$options = new SVGWithLogoAndCustomShapesOptions;
// SVG logo options (see extended class below)
$options->svgLogo = __DIR__.'/github.svg'; // logo from: https://github.com/simple-icons/simple-icons
$options->svgLogoScale = 0.25;
$options->svgLogoCssClass = 'qr-logo dark';
// QROptions
$options->version = 5;
$options->quietzoneSize = 4;
$options->outputType = QROutputInterface::CUSTOM;
$options->outputInterface = QRSvgWithLogoAndCustomShapes::class;
$options->outputBase64 = false;
$options->eccLevel = EccLevel::H; // ECC level H is required when using logos
$options->addQuietzone = true;
// https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient
$options->svgDefs = '
<linearGradient id="gradient" x1="100%" y2="100%">
<stop stop-color="#D70071" offset="0"/>
<stop stop-color="#9C4E97" offset="0.5"/>
<stop stop-color="#0035A9" offset="1"/>
</linearGradient>
<style><![CDATA[
.dark{fill: url(#gradient);}
.qr-logo{fill: #424242;}
]]></style>';
$out = (new QRCode($options))->render('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
if(PHP_SAPI !== 'cli'){
header('Content-type: image/svg+xml');
if(extension_loaded('zlib')){
header('Vary: Accept-Encoding');
header('Content-Encoding: gzip');
$out = gzencode($out, 9);
}
}
echo $out;
exit;