Passed
Push — main ( 709a03...3e1426 )
by smiley
02:31
created

QRMarkup::svgModule()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 3
nop 2
dl 0
loc 20
rs 9.6111
c 0
b 0
f 0
1
<?php
2
/**
3
 * Class QRMarkup
4
 *
5
 * @created      17.12.2016
6
 * @author       Smiley <[email protected]>
7
 * @copyright    2016 Smiley
8
 * @license      MIT
9
 */
10
11
namespace chillerlan\QRCode\Output;
12
13
use chillerlan\QRCode\Data\QRMatrix;
14
use chillerlan\QRCode\QRCode;
15
16
use function implode, is_string, ksort, sprintf, strip_tags, trim;
17
18
/**
19
 * Converts the matrix into markup types: HTML, SVG, ...
20
 */
21
class QRMarkup extends QROutputAbstract{
22
23
	protected string $defaultMode = QRCode::OUTPUT_MARKUP_SVG;
24
25
	/**
26
	 * @inheritDoc
27
	 */
28
	protected function moduleValueIsValid($value):bool{
29
		return is_string($value);
30
	}
31
32
	/**
33
	 * @inheritDoc
34
	 */
35
	protected function getModuleValue($value):string{
36
		return trim(strip_tags($value), " '\"\r\n\t");
37
	}
38
39
	/**
40
	 * @inheritDoc
41
	 */
42
	protected function getDefaultModuleValue(bool $isDark):string{
43
		return $isDark ? $this->options->markupDark : $this->options->markupLight;
44
	}
45
46
	/**
47
	 * HTML output
48
	 */
49
	protected function html(string $file = null):string{
50
51
		$html = empty($this->options->cssClass)
52
			? '<div>'
53
			: sprintf('<div class="%s">', $this->options->cssClass);
54
55
		$html .= $this->options->eol;
56
57
		foreach($this->matrix->matrix() as $row){
58
			$html .= '<div>';
59
60
			foreach($row as $M_TYPE){
61
				$html .= sprintf('<span style="background: %s;"></span>', $this->moduleValues[$M_TYPE]);
62
			}
63
64
			$html .= '</div>'.$this->options->eol;
65
		}
66
67
		$html .= '</div>'.$this->options->eol;
68
69
		if($file !== null){
70
			return sprintf(
71
				'<!DOCTYPE html><head><meta charset="UTF-8"><title>QR Code</title></head><body>%s</body>',
72
				$this->options->eol.$html
73
			);
74
		}
75
76
		return $html;
77
	}
78
79
	/**
80
	 * SVG output
81
	 *
82
	 * @see https://github.com/codemasher/php-qrcode/pull/5
83
	 * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg
84
	 * @see https://www.sarasoueidan.com/demos/interactive-svg-coordinate-system/
85
	 */
86
	protected function svg(string $file = null):string{
87
		$svg = $this->svgHeader();
88
89
		if(!empty($this->options->svgDefs)){
90
			$svg .= sprintf('<defs>%1$s%2$s</defs>%2$s', $this->options->svgDefs, $this->options->eol);
91
		}
92
93
		$svg .= $this->svgPaths();
94
95
		// close svg
96
		$svg .= sprintf('%1$s</svg>%1$s', $this->options->eol);
97
98
		// transform to data URI only when not saving to file
99
		if($file === null && $this->options->imageBase64){
100
			$svg = $this->base64encode($svg, 'image/svg+xml');
101
		}
102
103
		return $svg;
104
	}
105
106
	/**
107
	 * returns the <svg> header with the given options parsed
108
	 */
109
	protected function svgHeader():string{
110
		$width  = $this->options->svgWidth !== null ? sprintf(' width="%s"', $this->options->svgWidth) : '';
111
		$height = $this->options->svgHeight !== null ? sprintf(' height="%s"', $this->options->svgHeight) : '';
112
113
		/** @noinspection HtmlUnknownAttribute */
114
		return sprintf(
115
			'<?xml version="1.0" encoding="UTF-8"?>%6$s'.
116
			'<svg xmlns="http://www.w3.org/2000/svg" class="qr-svg %1$s" viewBox="0 0 %2$s %2$s" preserveAspectRatio="%3$s"%4$s%5$s>%6$s',
117
			$this->options->cssClass,
118
			$this->options->svgViewBoxSize ?? $this->moduleCount,
119
			$this->options->svgPreserveAspectRatio,
120
			$width,
121
			$height,
122
			$this->options->eol
123
		);
124
	}
125
126
	/**
127
	 * returns one or more SVG <path> elements
128
	 *
129
	 * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
130
	 */
131
	protected function svgPaths():string{
132
		$paths = [];
133
134
		// collect the modules for each type
135
		foreach($this->matrix->matrix() as $y => $row){
136
			foreach($row as $x => $M_TYPE){
137
138
				if($this->options->svgConnectPaths && !$this->matrix->checkTypes($x, $y, $this->options->svgExcludeFromConnect)){
0 ignored issues
show
Bug introduced by
It seems like $this->options->svgExcludeFromConnect can also be of type null; however, parameter $M_TYPES of chillerlan\QRCode\Data\QRMatrix::checkTypes() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

138
				if($this->options->svgConnectPaths && !$this->matrix->checkTypes($x, $y, /** @scrutinizer ignore-type */ $this->options->svgExcludeFromConnect)){
Loading history...
139
					// to connect paths we'll redeclare the $M_TYPE to data only
140
					$M_TYPE = QRMatrix::M_DATA;
141
142
					if($this->matrix->check($x, $y)){
143
						$M_TYPE |= QRMatrix::IS_DARK;
144
					}
145
				}
146
147
				// collect the modules per $M_TYPE
148
				$paths[$M_TYPE][] = $this->svgModule($x, $y);
149
			}
150
		}
151
152
		// beautify output
153
		ksort($paths);
154
155
		$svg = [];
156
157
		// create the path elements
158
		foreach($paths as $M_TYPE => $path){
159
			$path = trim(implode(' ', $path));
160
161
			if(empty($path)){
162
				continue;
163
			}
164
165
			$cssClass = implode(' ', [
166
				'qr-'.$M_TYPE,
167
				($M_TYPE & QRMatrix::IS_DARK) === QRMatrix::IS_DARK ? 'dark' : 'light',
168
				$this->options->cssClass,
169
			]);
170
171
			$format = empty($this->moduleValues[$M_TYPE])
172
				? '<path class="%1$s" d="%2$s"/>'
173
				: '<path class="%1$s" fill="%3$s" fill-opacity="%4$s" d="%2$s"/>';
174
175
			$svg[] = sprintf($format, $cssClass, $path, $this->moduleValues[$M_TYPE], $this->options->svgOpacity);
176
		}
177
178
		return implode($this->options->eol, $svg);
0 ignored issues
show
Bug introduced by
It seems like $this->options->eol can also be of type null; however, parameter $glue of implode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

178
		return implode(/** @scrutinizer ignore-type */ $this->options->eol, $svg);
Loading history...
179
	}
180
181
	/**
182
	 * returns a path segment for a single module
183
	 *
184
	 * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
185
	 */
186
	protected function svgModule(int $x, int $y):string{
187
188
		if($this->options->imageTransparent && !$this->matrix->check($x, $y)){
189
			return '';
190
		}
191
192
		if($this->options->drawCircularModules && !$this->matrix->checkTypes($x, $y, $this->options->keepAsSquare)){
0 ignored issues
show
Bug introduced by
It seems like $this->options->keepAsSquare can also be of type null; however, parameter $M_TYPES of chillerlan\QRCode\Data\QRMatrix::checkTypes() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

192
		if($this->options->drawCircularModules && !$this->matrix->checkTypes($x, $y, /** @scrutinizer ignore-type */ $this->options->keepAsSquare)){
Loading history...
193
			$r = $this->options->circleRadius;
194
195
			return sprintf(
196
				'M%1$s %2$s a%3$s %3$s 0 1 0 %4$s 0 a%3$s,%3$s 0 1 0 -%4$s 0Z',
197
				($x + 0.5 - $r),
198
				($y + 0.5),
199
				$r,
200
				($r * 2)
201
			);
202
203
		}
204
205
		return sprintf('M%1$s %2$s h%3$s v1 h-%4$sZ', $x, $y, 1, 1);
206
	}
207
208
}
209