Passed
Push — v5 ( 92f563...26536d )
by smiley
02:19
created

QRData::maskECC()   B

Complexity

Conditions 9
Paths 14

Size

Total Lines 60
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 37
c 1
b 0
f 0
nc 14
nop 0
dl 0
loc 60
rs 7.7724

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Class QRData
4
 *
5
 * @filesource   QRData.php
6
 * @created      25.11.2015
7
 * @package      chillerlan\QRCode\Data
8
 * @author       Smiley <[email protected]>
9
 * @copyright    2015 Smiley
10
 * @license      MIT
11
 */
12
13
namespace chillerlan\QRCode\Data;
14
15
use chillerlan\QRCode\Common\{EccLevel, Mode, ReedSolomon, Version};
16
use chillerlan\QRCode\QRCode;
17
use chillerlan\QRCode\Helpers\BitBuffer;
18
use chillerlan\Settings\SettingsContainerInterface;
19
20
use function range, sprintf;
21
22
/**
23
 * Processes the binary data and maps it on a matrix which is then being returned
24
 */
25
class QRData{
26
27
	/**
28
	 * current QR Code version
29
	 */
30
	protected Version $version;
31
32
	/**
33
	 * @var \chillerlan\QRCode\Data\QRDataModeInterface[]
34
	 */
35
	protected array $dataSegments = [];
36
37
	/**
38
	 * Max bits for the current ECC mode
39
	 *
40
	 * @var int[]
41
	 */
42
	protected array $maxBitsForEcc;
43
44
	/**
45
	 * the options instance
46
	 *
47
	 * @var \chillerlan\Settings\SettingsContainerInterface|\chillerlan\QRCode\QROptions
48
	 */
49
	protected SettingsContainerInterface $options;
50
51
	/**
52
	 * a BitBuffer instance
53
	 */
54
	protected BitBuffer $bitBuffer;
55
56
	protected EccLevel $eccLevel;
57
58
	/**
59
	 * QRData constructor.
60
	 *
61
	 * @param \chillerlan\Settings\SettingsContainerInterface $options
62
	 * @param array|null                                      $dataSegments
63
	 */
64
	public function __construct(SettingsContainerInterface $options, array $dataSegments = null){
65
		$this->options       = $options;
66
		$this->bitBuffer     = new BitBuffer;
67
		$this->eccLevel      = new EccLevel($this->options->eccLevel);
68
		$this->maxBitsForEcc = $this->eccLevel->getMaxBits();
69
70
		if(!empty($dataSegments)){
71
			$this->setData($dataSegments);
72
		}
73
74
	}
75
76
	/**
77
	 * Sets the data string (internally called by the constructor)
78
	 */
79
	public function setData(array $dataSegments):QRData{
80
81
		foreach($dataSegments as $segment){
82
			[$class, $data] = $segment;
83
84
			$this->dataSegments[] = new $class($data);
85
		}
86
87
		$version = $this->options->version === QRCode::VERSION_AUTO
88
			? $this->getMinimumVersion()
89
			: $this->options->version;
90
91
		$this->version = new Version($version);
92
93
		$this->writeBitBuffer();
94
95
		return $this;
96
	}
97
98
	/**
99
	 * returns a fresh matrix object with the data written for the given $maskPattern
100
	 */
101
	public function initMatrix(int $maskPattern, bool $test = null):QRMatrix{
102
		$rs   = new ReedSolomon($this->version, $this->eccLevel);
103
		$data = $rs->interleaveEcBytes($this->bitBuffer);
104
105
		return (new QRMatrix($this->version, $this->eccLevel))
106
			->init($maskPattern, $test)
107
			->mapData($data, $maskPattern)
108
		;
109
	}
110
111
	/**
112
	 * estimates the total length of the several mode segments in order to guess the minimum version
113
	 *
114
	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
115
	 */
116
	protected function estimateTotalBitLength():int{
117
		$length = 0;
118
		$margin = 0;
119
120
		foreach($this->dataSegments as $segment){
121
			// data length in bits of the current segment +4 bits for each mode descriptor
122
			$length += ($segment->getLengthInBits() + Mode::getLengthBitsForMode($segment->getDataMode())[0] + 4);
123
124
			if(!$segment instanceof ECI){
125
				// mode length bits margin to the next breakpoint
126
				$margin += ($segment instanceof Byte ? 8 : 2);
127
			}
128
		}
129
130
		foreach([9, 26, 40] as $breakpoint){
131
132
			// length bits for the first breakpoint have already been added
133
			if($breakpoint > 9){
134
				$length += $margin;
135
			}
136
137
			if($length < $this->maxBitsForEcc[$breakpoint]){
138
				return $length;
139
			}
140
		}
141
142
		throw new QRCodeDataException(sprintf('estimated data exceeds %d bits', $length));
143
	}
144
145
	/**
146
	 * returns the minimum version number for the given string
147
	 *
148
	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
149
	 */
150
	protected function getMinimumVersion():int{
151
		$total = $this->estimateTotalBitLength();
152
153
		// guess the version number within the given range
154
		foreach(range($this->options->versionMin, $this->options->versionMax) as $version){
155
156
			if($total <= $this->maxBitsForEcc[$version]){
157
				return $version;
158
			}
159
160
		}
161
162
		// it's almost impossible to run into this one as $this::estimateTotalBitLength() would throw first
163
		throw new QRCodeDataException('failed to guess minimum version'); // @codeCoverageIgnore
164
	}
165
166
	/**
167
	 * creates a BitBuffer and writes the string data to it
168
	 *
169
	 * @throws \chillerlan\QRCode\QRCodeException on data overflow
170
	 */
171
	protected function writeBitBuffer():void{
172
		$version  = $this->version->getVersionNumber();
173
		$MAX_BITS = $this->maxBitsForEcc[$version];
174
175
		foreach($this->dataSegments as $segment){
176
			$segment->write($this->bitBuffer, $version);
177
		}
178
179
		// overflow, likely caused due to invalid version setting
180
		if($this->bitBuffer->getLength() > $MAX_BITS){
181
			throw new QRCodeDataException(
182
				sprintf('code length overflow. (%d > %d bit)', $this->bitBuffer->getLength(), $MAX_BITS)
183
			);
184
		}
185
186
		// add terminator (ISO/IEC 18004:2000 Table 2)
187
		if($this->bitBuffer->getLength() + 4 <= $MAX_BITS){
188
			$this->bitBuffer->put(0b0000, 4);
189
		}
190
191
		// Padding: ISO/IEC 18004:2000 8.4.9 Bit stream to codeword conversion
192
193
		// if the final codeword is not exactly 8 bits in length, it shall be made 8 bits long
194
		// by the addition of padding bits with binary value 0
195
		while($this->bitBuffer->getLength() % 8 !== 0){
196
			$this->bitBuffer->putBit(false);
197
		}
198
199
		// The message bit stream shall then be extended to fill the data capacity of the symbol
200
		// corresponding to the Version and Error Correction Level, by the addition of the Pad
201
		// Codewords 11101100 and 00010001 alternately.
202
		while(true){
203
204
			if($this->bitBuffer->getLength() >= $MAX_BITS){
205
				break;
206
			}
207
208
			$this->bitBuffer->put(0b11101100, 8);
209
210
			if($this->bitBuffer->getLength() >= $MAX_BITS){
211
				break;
212
			}
213
214
			$this->bitBuffer->put(0b00010001, 8);
215
		}
216
217
	}
218
219
}
220