SvgWriter.php
6.6 KB
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
<?php
declare(strict_types=1);
/*
* (c) Jeroen van den Enden <info@endroid.nl>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Endroid\QrCode\Writer;
use Endroid\QrCode\Exception\GenerateImageException;
use Endroid\QrCode\Exception\MissingLogoHeightException;
use Endroid\QrCode\Exception\ValidationException;
use Endroid\QrCode\QrCodeInterface;
use SimpleXMLElement;
class SvgWriter extends AbstractWriter
{
public function writeString(QrCodeInterface $qrCode): string
{
$options = $qrCode->getWriterOptions();
if ($qrCode->getValidateResult()) {
throw new ValidationException('Built-in validation reader can not check SVG images: please disable via setValidateResult(false)');
}
$data = $qrCode->getData();
$svg = new SimpleXMLElement('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"/>');
$svg->addAttribute('version', '1.1');
$svg->addAttribute('width', $data['outer_width'].'px');
$svg->addAttribute('height', $data['outer_height'].'px');
$svg->addAttribute('viewBox', '0 0 '.$data['outer_width'].' '.$data['outer_height']);
$svg->addChild('defs');
// Block definition
$block_id = isset($options['rect_id']) && $options['rect_id'] ? $options['rect_id'] : 'block';
$blockDefinition = $svg->defs->addChild('rect');
$blockDefinition->addAttribute('id', $block_id);
$blockDefinition->addAttribute('width', strval($data['block_size']));
$blockDefinition->addAttribute('height', strval($data['block_size']));
$blockDefinition->addAttribute('fill', '#'.sprintf('%02x%02x%02x', $qrCode->getForegroundColor()['r'], $qrCode->getForegroundColor()['g'], $qrCode->getForegroundColor()['b']));
$blockDefinition->addAttribute('fill-opacity', strval($this->getOpacity($qrCode->getForegroundColor()['a'])));
// Background
$background = $svg->addChild('rect');
$background->addAttribute('x', '0');
$background->addAttribute('y', '0');
$background->addAttribute('width', strval($data['outer_width']));
$background->addAttribute('height', strval($data['outer_height']));
$background->addAttribute('fill', '#'.sprintf('%02x%02x%02x', $qrCode->getBackgroundColor()['r'], $qrCode->getBackgroundColor()['g'], $qrCode->getBackgroundColor()['b']));
$background->addAttribute('fill-opacity', strval($this->getOpacity($qrCode->getBackgroundColor()['a'])));
foreach ($data['matrix'] as $row => $values) {
foreach ($values as $column => $value) {
if (1 === $value) {
$block = $svg->addChild('use');
$block->addAttribute('x', strval($data['margin_left'] + $data['block_size'] * $column));
$block->addAttribute('y', strval($data['margin_left'] + $data['block_size'] * $row));
$block->addAttribute('xlink:href', '#'.$block_id, 'http://www.w3.org/1999/xlink');
}
}
}
$logoPath = $qrCode->getLogoPath();
if (is_string($logoPath)) {
$forceXlinkHref = false;
if (isset($options['force_xlink_href']) && $options['force_xlink_href']) {
$forceXlinkHref = true;
}
$this->addLogo($svg, $data['outer_width'], $data['outer_height'], $logoPath, $qrCode->getLogoWidth(), $qrCode->getLogoHeight(), $forceXlinkHref);
}
$xml = $svg->asXML();
if (!is_string($xml)) {
throw new GenerateImageException('Unable to save SVG XML');
}
if (isset($options['exclude_xml_declaration']) && $options['exclude_xml_declaration']) {
$xml = str_replace("<?xml version=\"1.0\"?>\n", '', $xml);
}
return $xml;
}
private function addLogo(SimpleXMLElement $svg, int $imageWidth, int $imageHeight, string $logoPath, int $logoWidth = null, int $logoHeight = null, bool $forceXlinkHref = false): void
{
$mimeType = $this->getMimeType($logoPath);
$imageData = file_get_contents($logoPath);
if (!is_string($imageData)) {
throw new GenerateImageException('Unable to read image data: check your logo path');
}
if ('image/svg+xml' === $mimeType && (null === $logoHeight || null === $logoWidth)) {
throw new MissingLogoHeightException('SVG Logos require an explicit height set via setLogoSize($width, $height)');
}
if (null === $logoHeight || null === $logoWidth) {
$logoImage = imagecreatefromstring(strval($imageData));
if (!$logoImage) {
throw new GenerateImageException('Unable to generate image: check your GD installation or logo path');
}
/** @var mixed $logoImage */
$logoSourceWidth = imagesx($logoImage);
$logoSourceHeight = imagesy($logoImage);
if (PHP_VERSION_ID < 80000) {
imagedestroy($logoImage);
}
if (null === $logoWidth) {
$logoWidth = $logoSourceWidth;
}
if (null === $logoHeight) {
$aspectRatio = $logoWidth / $logoSourceWidth;
$logoHeight = intval($logoSourceHeight * $aspectRatio);
}
}
$logoX = $imageWidth / 2 - $logoWidth / 2;
$logoY = $imageHeight / 2 - $logoHeight / 2;
$imageDefinition = $svg->addChild('image');
$imageDefinition->addAttribute('x', strval($logoX));
$imageDefinition->addAttribute('y', strval($logoY));
$imageDefinition->addAttribute('width', strval($logoWidth));
$imageDefinition->addAttribute('height', strval($logoHeight));
$imageDefinition->addAttribute('preserveAspectRatio', 'none');
// xlink:href is actually deprecated, but still required when placing the qr code in a pdf.
// SimpleXML strips out the xlink part by using addAttribute(), so it must be set directly.
if ($forceXlinkHref) {
$imageDefinition['xlink:href'] = 'data:'.$mimeType.';base64,'.base64_encode($imageData);
} else {
$imageDefinition->addAttribute('href', 'data:'.$mimeType.';base64,'.base64_encode($imageData));
}
}
private function getOpacity(int $alpha): float
{
$opacity = 1 - $alpha / 127;
return $opacity;
}
public static function getContentType(): string
{
return 'image/svg+xml';
}
public static function getSupportedExtensions(): array
{
return ['svg'];
}
public function getName(): string
{
return 'svg';
}
}