Passed
Push — main ( 7963d9...f1858f )
by smiley
02:10
created

QRMarkup::dump()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 4
eloc 12
nc 6
nop 1
dl 0
loc 18
rs 9.8666
c 1
b 0
f 1
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
	 * @inheritDoc
48
	 */
49
	public function dump(string $file = null){
50
		$file       ??= $this->options->cachefile;
51
		$saveToFile   = $file !== null;
52
53
		switch($this->options->outputType){
54
			case QRCode::OUTPUT_MARKUP_HTML:
55
				$data = $this->html($saveToFile);
56
				break;
57
			case QRCode::OUTPUT_MARKUP_SVG:
58
			default:
59
				$data = $this->svg($saveToFile);
60
		}
61
62
		if($saveToFile){
63
			$this->saveToFile($data, $file);
64
		}
65
66
		return $data;
67
	}
68
69
	/**
70
	 * HTML output
71
	 */
72
	protected function html(bool $saveToFile):string{
73
74
		$html = empty($this->options->cssClass)
75
			? '<div>'
76
			: sprintf('<div class="%s">', $this->options->cssClass);
77
78
		$html .= $this->options->eol;
79
80
		foreach($this->matrix->matrix() as $row){
81
			$html .= '<div>';
82
83
			foreach($row as $M_TYPE){
84
				$html .= sprintf('<span style="background: %s;"></span>', $this->moduleValues[$M_TYPE]);
85
			}
86
87
			$html .= '</div>'.$this->options->eol;
88
		}
89
90
		$html .= '</div>'.$this->options->eol;
91
92
		if($saveToFile){
93
			return sprintf(
94
				'<!DOCTYPE html><head><meta charset="UTF-8"><title>QR Code</title></head><body>%s</body>',
95
				$this->options->eol.$html
96
			);
97
		}
98
99
		return $html;
100
	}
101
102
	/**
103
	 * SVG output
104
	 *
105
	 * @see https://github.com/codemasher/php-qrcode/pull/5
106
	 * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg
107
	 * @see https://www.sarasoueidan.com/demos/interactive-svg-coordinate-system/
108
	 */
109
	protected function svg(bool $saveToFile):string{
110
		$svg = $this->svgHeader();
111
112
		if(!empty($this->options->svgDefs)){
113
			$svg .= sprintf('<defs>%1$s%2$s</defs>%2$s', $this->options->svgDefs, $this->options->eol);
114
		}
115
116
		$svg .= $this->svgPaths();
117
118
		// close svg
119
		$svg .= sprintf('%1$s</svg>%1$s', $this->options->eol);
120
121
		// transform to data URI only when not saving to file
122
		if(!$saveToFile && $this->options->imageBase64){
123
			$svg = $this->base64encode($svg, 'image/svg+xml');
124
		}
125
126
		return $svg;
127
	}
128
129
	/**
130
	 * returns the <svg> header with the given options parsed
131
	 */
132
	protected function svgHeader():string{
133
		$width  = $this->options->svgWidth !== null ? sprintf(' width="%s"', $this->options->svgWidth) : '';
134
		$height = $this->options->svgHeight !== null ? sprintf(' height="%s"', $this->options->svgHeight) : '';
135
136
		/** @noinspection HtmlUnknownAttribute */
137
		return sprintf(
138
			'<?xml version="1.0" encoding="UTF-8"?>%6$s'.
139
			'<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',
140
			$this->options->cssClass,
141
			$this->options->svgViewBoxSize ?? $this->moduleCount,
142
			$this->options->svgPreserveAspectRatio,
143
			$width,
144
			$height,
145
			$this->options->eol
146
		);
147
	}
148
149
	/**
150
	 * returns one or more SVG <path> elements
151
	 *
152
	 * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
153
	 */
154
	protected function svgPaths():string{
155
		$paths = [];
156
157
		// collect the modules for each type
158
		foreach($this->matrix->matrix() as $y => $row){
159
			foreach($row as $x => $M_TYPE){
160
161
				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

161
				if($this->options->svgConnectPaths && !$this->matrix->checkTypes($x, $y, /** @scrutinizer ignore-type */ $this->options->svgExcludeFromConnect)){
Loading history...
162
					// to connect paths we'll redeclare the $M_TYPE to data only
163
					$M_TYPE = QRMatrix::M_DATA;
164
165
					if($this->matrix->check($x, $y)){
166
						$M_TYPE |= QRMatrix::IS_DARK;
167
					}
168
				}
169
170
				// collect the modules per $M_TYPE
171
				$paths[$M_TYPE][] = $this->svgModule($x, $y);
172
			}
173
		}
174
175
		// beautify output
176
		ksort($paths);
177
178
		$svg = [];
179
180
		// create the path elements
181
		foreach($paths as $M_TYPE => $path){
182
			$path = trim(implode(' ', $path));
183
184
			if(empty($path)){
185
				continue;
186
			}
187
188
			$cssClass = implode(' ', [
189
				'qr-'.$M_TYPE,
190
				($M_TYPE & QRMatrix::IS_DARK) === QRMatrix::IS_DARK ? 'dark' : 'light',
191
				$this->options->cssClass,
192
			]);
193
194
			$format = empty($this->moduleValues[$M_TYPE])
195
				? '<path class="%1$s" d="%2$s"/>'
196
				: '<path class="%1$s" fill="%3$s" fill-opacity="%4$s" d="%2$s"/>';
197
198
			$svg[] = sprintf($format, $cssClass, $path, $this->moduleValues[$M_TYPE], $this->options->svgOpacity);
199
		}
200
201
		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

201
		return implode(/** @scrutinizer ignore-type */ $this->options->eol, $svg);
Loading history...
202
	}
203
204
	/**
205
	 * returns a path segment for a single module
206
	 *
207
	 * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
208
	 */
209
	protected function svgModule(int $x, int $y):string{
210
211
		if($this->options->imageTransparent && !$this->matrix->check($x, $y)){
212
			return '';
213
		}
214
215
		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

215
		if($this->options->drawCircularModules && !$this->matrix->checkTypes($x, $y, /** @scrutinizer ignore-type */ $this->options->keepAsSquare)){
Loading history...
216
			$r = $this->options->circleRadius;
217
218
			return sprintf(
219
				'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',
220
				($x + 0.5 - $r),
221
				($y + 0.5),
222
				$r,
223
				($r * 2)
224
			);
225
226
		}
227
228
		return sprintf('M%1$s %2$s h%3$s v1 h-%4$sZ', $x, $y, 1, 1);
229
	}
230
231
}
232