1<?php
2
3namespace Mpdf\QrCode\Output;
4
5use Mpdf\QrCode\QrCode;
6use SimpleXMLElement;
7
8class Svg
9{
10
11	/**
12	 * @param QrCode $qrCode	 QR code instance
13	 * @param int	$size	   The width / height of the resulting SVG
14	 * @param string $background The background color, e. g. "white", "rgb(0,0,0)" or "cmyk(0,0,0,0)"
15	 * @param string $color	  The foreground and border color, e. g. "black", "rgb(255,255,255)" or "cmyk(0,0,0,100)"
16	 *
17	 * @return string Binary image data
18	 */
19	public function output(QrCode $qrCode, $size = 100, $background = 'white', $color = 'black')
20	{
21		$qrSize = $qrCode->getQrSize();
22		$final  = $qrCode->getFinal();
23
24		if ($qrCode->isBorderDisabled()) {
25			$minSize = 4;
26			$maxSize = $qrSize - 4;
27		} else {
28			$minSize = 0;
29			$maxSize = $qrSize;
30		}
31
32		$rectSize = $size / ($maxSize - $minSize);
33
34		$svg = new SimpleXMLElement('<svg></svg>');
35		$svg->addAttribute('version', '1.1');
36		$svg->addAttribute('xmlns', 'http://www.w3.org/2000/svg');
37		$svg->addAttribute('width', $size);
38		$svg->addAttribute('height', $size);
39
40		$this->addChild(
41			$svg,
42			'rect',
43			[
44				'x'	  => 0,
45				'y'	  => 0,
46				'width'  => $size,
47				'height' => $size,
48				'fill'   => $background,
49			]
50		);
51
52		for ($row = $minSize; $row < $maxSize; $row++) {
53			// Simple compression: pixels in a row will be compressed into the same rectangle.
54			$startX = null;
55			$y = ($row - $minSize) * $rectSize;
56			for ($column = $minSize; $column < $maxSize; $column++) {
57				$x = ($column - $minSize) * $rectSize;
58				if ($final[$column + $row * $qrSize + 1]) {
59					if ($startX === null) {
60						$startX = $x;
61					}
62				} elseif ($startX !== null) {
63					$this->addChild(
64						$svg,
65						'rect',
66						[
67							'x'      => $startX,
68							'y'      => $y,
69							'width'  => $x - $startX,
70							'height' => $rectSize,
71							'fill'   => $color,
72						]
73					);
74					$startX = null;
75				}
76			}
77
78			if ($startX !== null) {
79				$x = ($column - $minSize) * $rectSize;
80				$this->addChild(
81					$svg,
82					'rect',
83					[
84						'x'      => $startX,
85						'y'      => $y,
86						'width'  => $x - $startX,
87						'height' => $rectSize,
88						'fill'   => $color,
89					]
90				);
91			}
92		}
93
94		for ($column = $minSize; $column < $maxSize; $column++) {
95			// Simple compression: pixels in a column will be compressed into the same rectangle.
96			$startY = null;
97			$x	  = ($column - $minSize) * $rectSize;
98			for ($row = $minSize; $row < $maxSize; $row++) {
99				$y = ($row - $minSize) * $rectSize;
100				if ($final[$column + $row * $qrSize + 1]) {
101					if ($startY === null) {
102						$startY = $y;
103					}
104				} elseif ($startY !== null) {
105					if ($startY < $y - $rectSize) {
106						// Only drawn 2+ columns
107						$this->addChild(
108							$svg,
109							'rect',
110							[
111								'x'      => $x,
112								'y'      => $startY,
113								'width'  => $rectSize,
114								'height' => $y - $startY,
115								'fill'   => $color,
116							]
117						);
118					}
119					$startY = null;
120				}
121			}
122
123			if ($startY !== null) {
124				$y = ($row - $minSize) * $rectSize;
125				$this->addChild(
126					$svg,
127					'rect',
128					[
129						'x'      => $x,
130						'y'      => $startY,
131						'width'  => $rectSize,
132						'height' => $y - $startY,
133						'fill'   => $color,
134					]
135				);
136			}
137		}
138
139		return $svg->asXML();
140	}
141
142
143	/**
144	 * Adds a child with the given attributes
145	 *
146	 * @param SimpleXMLElement $svg
147	 * @param string		   $name
148	 * @param array			$attributes
149	 *
150	 * @return SimpleXMLElement
151	 */
152	public function addChild(SimpleXMLElement $svg, $name, array $attributes = [])
153	{
154		$child = $svg->addChild($name);
155
156		foreach ($attributes as $key => $value) {
157			$child->addAttribute((string) $key, (string) $value);
158		}
159
160		return $child;
161	}
162}
163