Skip to content

Commit 465351c

Browse files
committed
✨ better SVG output
1 parent 0929aa0 commit 465351c

File tree

3 files changed

+81
-42
lines changed

3 files changed

+81
-42
lines changed

examples/svg.php

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,39 +17,51 @@
1717
$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s';
1818

1919
$options = new QROptions([
20-
'version' => 5,
20+
'version' => 20,
2121
'outputType' => QRCode::OUTPUT_MARKUP_SVG,
2222
'eccLevel' => QRCode::ECC_L,
23-
'scale' => 10,
23+
'scale' => 5,
24+
'addQuietzone' => true,
25+
'svgOpacity' => 0.8,
26+
'svgDefs' => '
27+
<linearGradient id="g2">
28+
<stop offset="0%" stop-color="#39F" />
29+
<stop offset="100%" stop-color="#F3F" />
30+
</linearGradient>
31+
<linearGradient id="g1">
32+
<stop offset="0%" stop-color="#F3F" />
33+
<stop offset="100%" stop-color="#39F" />
34+
</linearGradient>
35+
<style>rect{shape-rendering:crispEdges}</style>',
2436
'moduleValues' => [
2537
// finder
26-
1536 => '#A71111', // dark (true)
27-
6 => '#FFBFBF', // light (false)
38+
1536 => 'url(#g1)', // dark (true)
39+
6 => '#eee', // light (false)
2840
// alignment
29-
2560 => '#A70364',
30-
10 => '#FFC9C9',
41+
2560 => 'url(#g1)',
42+
10 => '#eee',
3143
// timing
32-
3072 => '#98005D',
33-
12 => '#FFB8E9',
44+
3072 => 'url(#g1)',
45+
12 => '#eee',
3446
// format
35-
3584 => '#003804',
36-
14 => '#00FB12',
47+
3584 => 'url(#g1)',
48+
14 => '#eee',
3749
// version
38-
4096 => '#650098',
39-
16 => '#E0B8FF',
50+
4096 => 'url(#g1)',
51+
16 => '#eee',
4052
// data
41-
1024 => '#4A6000',
42-
4 => '#ECF9BE',
53+
1024 => 'url(#g2)',
54+
4 => '#eee',
4355
// darkmodule
44-
512 => '#080063',
56+
512 => 'url(#g1)',
4557
// separator
46-
8 => '#AFBFBF',
58+
8 => '#eee',
4759
// quietzone
48-
18 => '#FFFFFF',
60+
18 => '#eee',
4961
],
5062
]);
5163

52-
header('Content-type: image/svg+xml');
64+
#header('Content-type: image/svg+xml');
5365

5466
echo (new QRCode($options))->render($data);
5567

src/Output/QRMarkup.php

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class QRMarkup extends QROutputAbstract{
2121

2222
/**
2323
* @return string
24+
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
2425
*/
2526
public function dump(){
2627

@@ -68,52 +69,60 @@ protected function toHTML(){
6869
* @return string|bool
6970
*/
7071
protected function toSVG(){
71-
$length = ($this->moduleCount + ($this->options->addQuietzone ? 8 : 0)) * $this->options->scale;
72+
$scale = $this->options->scale;
73+
$length = $this->moduleCount * $scale;
74+
$matrix = $this->matrix->matrix();
7275

73-
// svg header
74-
$svg = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="'.$length.'" height="'.$length.'" viewBox="0 0 '.$length.' '.$length.'">'.$this->options->eol.
75-
'<defs><style>rect{shape-rendering:crispEdges}</style></defs>'.$this->options->eol;
76+
$svg = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="'.$length.'px" height="'.$length.'px">'
77+
.$this->options->eol
78+
.'<defs>'.$this->options->svgDefs.'</defs>'
79+
.$this->options->eol;
7680

77-
// @todo: optimize -> see https://github.com/alexeyten/qr-image/blob/master/lib/vector.js
78-
foreach($this->options->moduleValues as $key => $value){
81+
foreach($this->options->moduleValues as $M_TYPE => $value){
7982

8083
// fallback
8184
if(is_bool($value)){
8285
$value = $value ? '#000' : '#fff';
8386
}
8487

85-
// svg body
86-
foreach($this->matrix->matrix() as $y => $row){
88+
$path = '';
89+
90+
foreach($matrix as $y => $row){
8791
//we'll combine active blocks within a single row as a lightweight compression technique
88-
$from = -1;
92+
$start = null;
8993
$count = 0;
9094

91-
foreach($row as $x => $pixel){
92-
if($pixel === $key){
95+
foreach($row as $x => $module){
96+
97+
if($module === $M_TYPE){
9398
$count++;
9499

95-
if($from < 0){
96-
$from = $x;
100+
if($start === null){
101+
$start = $x * $scale;
102+
}
103+
104+
if($row[$x + 1] ?? false){
105+
continue;
97106
}
98107
}
99-
elseif($from >= 0){
100-
$svg .= '<rect x="'.($from * $this->options->scale).'" y="'.($y * $this->options->scale)
101-
.'" width="'.($this->options->scale * $count).'" height="'.$this->options->scale.'" fill="'.$value.'"'
102-
.(trim($this->options->cssClass) !== '' ? ' class="'.$this->options->cssClass.'"' :'').' />'
103-
.$this->options->eol;
108+
109+
if($count > 0){
110+
$len = $count * $scale;
111+
$path .= 'M' .$start. ' ' .($y * $scale). ' h'.$len.' v'.$scale.' h-'.$len.'Z ';
104112

105113
// reset count
106-
$from = -1;
107114
$count = 0;
115+
$start = null;
108116
}
109-
}
110117

111-
// close off the row, if applicable
112-
if($from >= 0){
113-
$svg .= '<rect x="'.($from * $this->options->scale).'" y="'.($y * $this->options->scale)
114-
.'" width="'.($this->options->scale * $count).'" height="'.$this->options->scale.'" class="'.$this->options->cssClass.'" fill="'.$value.'" />'.$this->options->eol;
115118
}
119+
116120
}
121+
122+
if(!empty($path)){
123+
$svg .= '<path class="qr-'.$M_TYPE.' '.$this->options->cssClass.'" stroke="transparent" fill="'.$value.'" fill-opacity="'.$this->options->svgOpacity.'" d="'.$path.'" />';
124+
}
125+
117126
}
118127

119128
// close svg

src/QROptions.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
* @property int $scale
3333
*
3434
* @property string $cssClass
35+
* @property string $svgOpacity
36+
* @property string $svgDefs
3537
*
3638
* @property string $textDark
3739
* @property string $textLight
@@ -154,6 +156,22 @@ class QROptions{
154156
*/
155157
protected $cssClass;
156158

159+
/**
160+
* SVG opacity
161+
*
162+
* @var float
163+
*/
164+
protected $svgOpacity = 1.0;
165+
166+
/**
167+
* anything between <defs>
168+
*
169+
* @see https://developer.mozilla.org/docs/Web/SVG/Element/defs
170+
*
171+
* @var string
172+
*/
173+
protected $svgDefs = '<style>rect{shape-rendering:crispEdges}</style>';
174+
157175
/**
158176
* string substitute for dark
159177
*

0 commit comments

Comments
 (0)