PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.4
Booking for Appointments and Events Calendar – Amelia v2.4.4
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / vendor / bacon / bacon-qr-code / src / Renderer / Image / SvgImageBackEnd.php
ameliabooking / vendor / bacon / bacon-qr-code / src / Renderer / Image Last commit date
EpsImageBackEnd.php 8 months ago ImageBackEndInterface.php 8 months ago ImagickImageBackEnd.php 8 months ago SvgImageBackEnd.php 8 months ago TransformationMatrix.php 8 months ago
SvgImageBackEnd.php
370 lines
1 <?php
2 declare(strict_types = 1);
3
4 namespace BaconQrCode\Renderer\Image;
5
6 use BaconQrCode\Exception\RuntimeException;
7 use BaconQrCode\Renderer\Color\Alpha;
8 use BaconQrCode\Renderer\Color\ColorInterface;
9 use BaconQrCode\Renderer\Path\Close;
10 use BaconQrCode\Renderer\Path\Curve;
11 use BaconQrCode\Renderer\Path\EllipticArc;
12 use BaconQrCode\Renderer\Path\Line;
13 use BaconQrCode\Renderer\Path\Move;
14 use BaconQrCode\Renderer\Path\Path;
15 use BaconQrCode\Renderer\RendererStyle\Gradient;
16 use BaconQrCode\Renderer\RendererStyle\GradientType;
17 use XMLWriter;
18
19 final class SvgImageBackEnd implements ImageBackEndInterface
20 {
21 private const PRECISION = 3;
22
23 /**
24 * @var XMLWriter|null
25 */
26 private $xmlWriter;
27
28 /**
29 * @var int[]|null
30 */
31 private $stack;
32
33 /**
34 * @var int|null
35 */
36 private $currentStack;
37
38 /**
39 * @var int|null
40 */
41 private $gradientCount;
42
43 public function __construct()
44 {
45 if (! class_exists(XMLWriter::class)) {
46 throw new RuntimeException('You need to install the libxml extension to use this back end');
47 }
48 }
49
50 public function new(int $size, ColorInterface $backgroundColor) : void
51 {
52 $this->xmlWriter = new XMLWriter();
53 $this->xmlWriter->openMemory();
54
55 $this->xmlWriter->startDocument('1.0', 'UTF-8');
56 $this->xmlWriter->startElement('svg');
57 $this->xmlWriter->writeAttribute('xmlns', 'http://www.w3.org/2000/svg');
58 $this->xmlWriter->writeAttribute('version', '1.1');
59 $this->xmlWriter->writeAttribute('width', (string) $size);
60 $this->xmlWriter->writeAttribute('height', (string) $size);
61 $this->xmlWriter->writeAttribute('viewBox', '0 0 '. $size . ' ' . $size);
62
63 $this->gradientCount = 0;
64 $this->currentStack = 0;
65 $this->stack[0] = 0;
66
67 $alpha = 1;
68
69 if ($backgroundColor instanceof Alpha) {
70 $alpha = $backgroundColor->getAlpha() / 100;
71 }
72
73 if (0 === $alpha) {
74 return;
75 }
76
77 $this->xmlWriter->startElement('rect');
78 $this->xmlWriter->writeAttribute('x', '0');
79 $this->xmlWriter->writeAttribute('y', '0');
80 $this->xmlWriter->writeAttribute('width', (string) $size);
81 $this->xmlWriter->writeAttribute('height', (string) $size);
82 $this->xmlWriter->writeAttribute('fill', $this->getColorString($backgroundColor));
83
84 if ($alpha < 1) {
85 $this->xmlWriter->writeAttribute('fill-opacity', (string) $alpha);
86 }
87
88 $this->xmlWriter->endElement();
89 }
90
91 public function scale(float $size) : void
92 {
93 if (null === $this->xmlWriter) {
94 throw new RuntimeException('No image has been started');
95 }
96
97 $this->xmlWriter->startElement('g');
98 $this->xmlWriter->writeAttribute(
99 'transform',
100 sprintf('scale(%s)', round($size, self::PRECISION))
101 );
102 ++$this->stack[$this->currentStack];
103 }
104
105 public function translate(float $x, float $y) : void
106 {
107 if (null === $this->xmlWriter) {
108 throw new RuntimeException('No image has been started');
109 }
110
111 $this->xmlWriter->startElement('g');
112 $this->xmlWriter->writeAttribute(
113 'transform',
114 sprintf('translate(%s,%s)', round($x, self::PRECISION), round($y, self::PRECISION))
115 );
116 ++$this->stack[$this->currentStack];
117 }
118
119 public function rotate(int $degrees) : void
120 {
121 if (null === $this->xmlWriter) {
122 throw new RuntimeException('No image has been started');
123 }
124
125 $this->xmlWriter->startElement('g');
126 $this->xmlWriter->writeAttribute('transform', sprintf('rotate(%d)', $degrees));
127 ++$this->stack[$this->currentStack];
128 }
129
130 public function push() : void
131 {
132 if (null === $this->xmlWriter) {
133 throw new RuntimeException('No image has been started');
134 }
135
136 $this->xmlWriter->startElement('g');
137 $this->stack[] = 1;
138 ++$this->currentStack;
139 }
140
141 public function pop() : void
142 {
143 if (null === $this->xmlWriter) {
144 throw new RuntimeException('No image has been started');
145 }
146
147 for ($i = 0; $i < $this->stack[$this->currentStack]; ++$i) {
148 $this->xmlWriter->endElement();
149 }
150
151 array_pop($this->stack);
152 --$this->currentStack;
153 }
154
155 public function drawPathWithColor(Path $path, ColorInterface $color) : void
156 {
157 if (null === $this->xmlWriter) {
158 throw new RuntimeException('No image has been started');
159 }
160
161 $alpha = 1;
162
163 if ($color instanceof Alpha) {
164 $alpha = $color->getAlpha() / 100;
165 }
166
167 $this->startPathElement($path);
168 $this->xmlWriter->writeAttribute('fill', $this->getColorString($color));
169
170 if ($alpha < 1) {
171 $this->xmlWriter->writeAttribute('fill-opacity', (string) $alpha);
172 }
173
174 $this->xmlWriter->endElement();
175 }
176
177 public function drawPathWithGradient(
178 Path $path,
179 Gradient $gradient,
180 float $x,
181 float $y,
182 float $width,
183 float $height
184 ) : void {
185 if (null === $this->xmlWriter) {
186 throw new RuntimeException('No image has been started');
187 }
188
189 $gradientId = $this->createGradientFill($gradient, $x, $y, $width, $height);
190 $this->startPathElement($path);
191 $this->xmlWriter->writeAttribute('fill', 'url(#' . $gradientId . ')');
192 $this->xmlWriter->endElement();
193 }
194
195 public function done() : string
196 {
197 if (null === $this->xmlWriter) {
198 throw new RuntimeException('No image has been started');
199 }
200
201 foreach ($this->stack as $openElements) {
202 for ($i = $openElements; $i > 0; --$i) {
203 $this->xmlWriter->endElement();
204 }
205 }
206
207 $this->xmlWriter->endDocument();
208 $blob = $this->xmlWriter->outputMemory(true);
209 $this->xmlWriter = null;
210 $this->stack = null;
211 $this->currentStack = null;
212 $this->gradientCount = null;
213
214 return $blob;
215 }
216
217 private function startPathElement(Path $path) : void
218 {
219 $pathData = [];
220
221 foreach ($path as $op) {
222 switch (true) {
223 case $op instanceof Move:
224 $pathData[] = sprintf(
225 'M%s %s',
226 round($op->getX(), self::PRECISION),
227 round($op->getY(), self::PRECISION)
228 );
229 break;
230
231 case $op instanceof Line:
232 $pathData[] = sprintf(
233 'L%s %s',
234 round($op->getX(), self::PRECISION),
235 round($op->getY(), self::PRECISION)
236 );
237 break;
238
239 case $op instanceof EllipticArc:
240 $pathData[] = sprintf(
241 'A%s %s %s %u %u %s %s',
242 round($op->getXRadius(), self::PRECISION),
243 round($op->getYRadius(), self::PRECISION),
244 round($op->getXAxisAngle(), self::PRECISION),
245 $op->isLargeArc(),
246 $op->isSweep(),
247 round($op->getX(), self::PRECISION),
248 round($op->getY(), self::PRECISION)
249 );
250 break;
251
252 case $op instanceof Curve:
253 $pathData[] = sprintf(
254 'C%s %s %s %s %s %s',
255 round($op->getX1(), self::PRECISION),
256 round($op->getY1(), self::PRECISION),
257 round($op->getX2(), self::PRECISION),
258 round($op->getY2(), self::PRECISION),
259 round($op->getX3(), self::PRECISION),
260 round($op->getY3(), self::PRECISION)
261 );
262 break;
263
264 case $op instanceof Close:
265 $pathData[] = 'Z';
266 break;
267
268 default:
269 throw new RuntimeException('Unexpected draw operation: ' . get_class($op));
270 }
271 }
272
273 $this->xmlWriter->startElement('path');
274 $this->xmlWriter->writeAttribute('fill-rule', 'evenodd');
275 $this->xmlWriter->writeAttribute('d', implode('', $pathData));
276 }
277
278 private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : string
279 {
280 $this->xmlWriter->startElement('defs');
281
282 $startColor = $gradient->getStartColor();
283 $endColor = $gradient->getEndColor();
284
285 if ($gradient->getType() === GradientType::RADIAL()) {
286 $this->xmlWriter->startElement('radialGradient');
287 } else {
288 $this->xmlWriter->startElement('linearGradient');
289 }
290
291 $this->xmlWriter->writeAttribute('gradientUnits', 'userSpaceOnUse');
292
293 switch ($gradient->getType()) {
294 case GradientType::HORIZONTAL():
295 $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
296 $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION));
297 $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION));
298 $this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION));
299 break;
300
301 case GradientType::VERTICAL():
302 $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
303 $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION));
304 $this->xmlWriter->writeAttribute('x2', (string) round($x, self::PRECISION));
305 $this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION));
306 break;
307
308 case GradientType::DIAGONAL():
309 $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
310 $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION));
311 $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION));
312 $this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION));
313 break;
314
315 case GradientType::INVERSE_DIAGONAL():
316 $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
317 $this->xmlWriter->writeAttribute('y1', (string) round($y + $height, self::PRECISION));
318 $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION));
319 $this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION));
320 break;
321
322 case GradientType::RADIAL():
323 $this->xmlWriter->writeAttribute('cx', (string) round(($x + $width) / 2, self::PRECISION));
324 $this->xmlWriter->writeAttribute('cy', (string) round(($y + $height) / 2, self::PRECISION));
325 $this->xmlWriter->writeAttribute('r', (string) round(max($width, $height) / 2, self::PRECISION));
326 break;
327 }
328
329 $id = sprintf('g%d', ++$this->gradientCount);
330 $this->xmlWriter->writeAttribute('id', $id);
331
332 $this->xmlWriter->startElement('stop');
333 $this->xmlWriter->writeAttribute('offset', '0%');
334 $this->xmlWriter->writeAttribute('stop-color', $this->getColorString($startColor));
335
336 if ($startColor instanceof Alpha) {
337 $this->xmlWriter->writeAttribute('stop-opacity', (string) $startColor->getAlpha());
338 }
339
340 $this->xmlWriter->endElement();
341
342 $this->xmlWriter->startElement('stop');
343 $this->xmlWriter->writeAttribute('offset', '100%');
344 $this->xmlWriter->writeAttribute('stop-color', $this->getColorString($endColor));
345
346 if ($endColor instanceof Alpha) {
347 $this->xmlWriter->writeAttribute('stop-opacity', (string) $endColor->getAlpha());
348 }
349
350 $this->xmlWriter->endElement();
351
352 $this->xmlWriter->endElement();
353 $this->xmlWriter->endElement();
354
355 return $id;
356 }
357
358 private function getColorString(ColorInterface $color) : string
359 {
360 $color = $color->toRgb();
361
362 return sprintf(
363 '#%02x%02x%02x',
364 $color->getRed(),
365 $color->getGreen(),
366 $color->getBlue()
367 );
368 }
369 }
370