Completed
Push — master ( 9ff756...275bd9 )
by smiley
02:34
created

QRCode::setData()   D

Complexity

Conditions 9
Paths 19

Size

Total Lines 45
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 10
Bugs 0 Features 1
Metric Value
c 10
b 0
f 1
dl 0
loc 45
rs 4.909
cc 9
eloc 30
nc 19
nop 2
1
<?php
2
/**
3
 * Class QRCode
4
 *
5
 * @filesource   QRCode.php
6
 * @created      26.11.2015
7
 * @package      chillerlan\QRCode
8
 * @author       Smiley <[email protected]>
9
 * @copyright    2015 Smiley
10
 * @license      MIT
11
 */
12
13
namespace chillerlan\QRCode;
14
15
use chillerlan\QRCode\Data\AlphaNum;
16
use chillerlan\QRCode\Data\Byte;
17
use chillerlan\QRCode\Data\Kanji;
18
use chillerlan\QRCode\Data\Number;
19
use chillerlan\QRCode\Output\QROutputInterface;
20
21
/**
22
 * @link https://github.com/kazuhikoarase/qrcode-generator/tree/master/php
23
 * @link http://www.thonky.com/qr-code-tutorial/
24
 */
25
class QRCode{
26
27
	/**
28
	 * API constants
29
	 */
30
	const OUTPUT_STRING_TEXT = 0;
31
	const OUTPUT_STRING_JSON = 1;
32
	const OUTPUT_STRING_HTML = 2;
33
34
	const OUTPUT_IMAGE_PNG = 'png';
35
	const OUTPUT_IMAGE_JPG = 'jpg';
36
	const OUTPUT_IMAGE_GIF = 'gif';
37
38
	const ERROR_CORRECT_LEVEL_L = 1; // 7%.
39
	const ERROR_CORRECT_LEVEL_M = 0; // 15%.
40
	const ERROR_CORRECT_LEVEL_Q = 3; // 25%.
41
	const ERROR_CORRECT_LEVEL_H = 2; // 30%.
42
43
	// max bits @ ec level L:07 M:15 Q:25 H:30 %
44
	const TYPE_01 =  1; //  152  128  104   72
45
	const TYPE_02 =  2; //  272  224  176  128
46
	const TYPE_03 =  3; //  440  352  272  208
47
	const TYPE_04 =  4; //  640  512  384  288
48
	const TYPE_05 =  5; //  864  688  496  368
49
	const TYPE_06 =  6; // 1088  864  608  480
50
	const TYPE_07 =  7; // 1248  992  704  528
51
	const TYPE_08 =  8; // 1552 1232  880  688
52
	const TYPE_09 =  9; // 1856 1456 1056  800
53
	const TYPE_10 = 10; // 2192 1728 1232  976
54
55
	/**
56
	 * @var array
57
	 */
58
	protected $matrix = [];
59
60
	/**
61
	 * @var int
62
	 */
63
	protected $pixelCount = 0;
64
65
	/**
66
	 * @var int
67
	 */
68
	protected $typeNumber;
69
70
	/**
71
	 * @var int
72
	 */
73
	protected $errorCorrectLevel;
74
75
	/**
76
	 * @var int
77
	 */
78
	protected $lostPoint;
79
80
	/**
81
	 * @var int
82
	 */
83
	protected $darkCount;
84
85
	/**
86
	 * @var int
87
	 */
88
	protected $minLostPoint;
89
90
	/**
91
	 * @var int
92
	 */
93
	protected $maskPattern;
94
95
	/**
96
	 * @var \chillerlan\QRCode\BitBuffer
97
	 */
98
	protected $bitBuffer;
99
100
	/**
101
	 * @var \chillerlan\QRCode\Data\QRDataInterface
102
	 */
103
	protected $qrDataInterface;
104
105
	/**
106
	 * @var \chillerlan\QRCode\Output\QROutputInterface
107
	 */
108
	protected $qrOutputInterface;
109
110
	/**
111
	 * QRCode constructor.
112
	 *
113
	 * @param string                                      $data
114
	 * @param \chillerlan\QRCode\Output\QROutputInterface $output
115
	 * @param \chillerlan\QRCode\QROptions|null           $options
116
	 */
117
	public function __construct($data, QROutputInterface $output, QROptions $options = null){
118
		$this->qrOutputInterface = $output;
119
		$this->bitBuffer = new BitBuffer;
120
		$this->setData($data, $options);
121
	}
122
123
	/**
124
	 * @param string                            $data
125
	 * @param \chillerlan\QRCode\QROptions|null $options
126
	 *
127
	 * @return $this
128
	 * @throws \chillerlan\QRCode\QRCodeException
129
	 */
130
	public function setData($data, QROptions $options = null){
131
		$data = trim($data);
132
133
		if(empty($data)){
134
			throw new QRCodeException('No data given.');
135
		}
136
137
		if(!$options instanceof QROptions){
138
			$options = new QROptions;
139
		}
140
141
		if(!in_array($options->errorCorrectLevel, QRConst::RSBLOCK, true)){
142
			throw new QRCodeException('Invalid error correct level: '.$options->errorCorrectLevel);
143
		}
144
145
		$this->errorCorrectLevel = $options->errorCorrectLevel;
146
147
		switch(true){
148
			case Util::isAlphaNum($data):
149
				$mode = Util::isNumber($data) ? QRConst::MODE_NUMBER : QRConst::MODE_ALPHANUM;
150
				break;
151
			case Util::isKanji($data):
152
				$mode = QRConst::MODE_KANJI;
153
				break;
154
			default:
155
				$mode = QRConst::MODE_BYTE;
156
				break;
157
		}
158
159
		$qrDataInterface = [
160
			QRConst::MODE_ALPHANUM => AlphaNum::class,
161
			QRConst::MODE_BYTE     => Byte::class,
162
			QRConst::MODE_KANJI    => Kanji::class,
163
			QRConst::MODE_NUMBER   => Number::class,
164
		][$mode];
165
166
		$this->qrDataInterface = new $qrDataInterface($data);
167
		$this->typeNumber = intval($options->typeNumber);
168
169
		if($this->typeNumber < 1 || $this->typeNumber > 10){
170
			$this->typeNumber = $this->getTypeNumber($mode);
171
		}
172
173
		return $this;
174
	}
175
176
	/**
177
	 * @param $mode
178
	 *
179
	 * @return int
180
	 * @throws \chillerlan\QRCode\QRCodeException
181
	 */
182
	protected function getTypeNumber($mode){
183
		$length = $this->qrDataInterface->dataLength;
0 ignored issues
show
Bug introduced by
Accessing dataLength on the interface chillerlan\QRCode\Data\QRDataInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
184
185
		if($this->qrDataInterface->mode === QRConst::MODE_KANJI){
0 ignored issues
show
Bug introduced by
Accessing mode on the interface chillerlan\QRCode\Data\QRDataInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
186
			$length = floor($this->qrDataInterface->dataLength / 2);
0 ignored issues
show
Bug introduced by
Accessing dataLength on the interface chillerlan\QRCode\Data\QRDataInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
187
		}
188
189
		foreach(range(1, 10) as $type){
190
			if($length <= Util::getMaxLength($type, $mode, $this->errorCorrectLevel)){
191
				return $type;
192
			}
193
		}
194
195
		throw new QRCodeException('Unable to determine type number.'); // @codeCoverageIgnore
196
	}
197
198
	/**
199
	 * @return mixed
200
	 */
201
	public function output(){
202
		$this->qrOutputInterface->setMatrix($this->getRawData());
203
		return $this->qrOutputInterface->dump();
204
	}
205
206
	/**
207
	 * @return array
208
	 */
209
	public function getRawData(){
210
		$this->minLostPoint = 0;
211
		$this->maskPattern = 0;
212
213
		foreach(range(0, 7) as $pattern){
214
			$this->testPattern($pattern);
215
		}
216
217
		$this->getMatrix(false, $this->maskPattern);
218
219
		return $this->matrix;
220
	}
221
222
	/**
223
	 * @param array $range
224
	 */
225
	protected function testLevel1(array $range){
226
227
		foreach($range as $row){
228
			foreach($range as $col){
229
				$sameCount = 0;
230
231
				foreach([-1, 0, 1] as $rr){
232
					if($row + $rr < 0 || $this->pixelCount <= $row + $rr){
233
						continue;
234
					}
235
236
					foreach([-1, 0, 1] as $cr){
237
238
						if(($rr === 0 && $cr === 0) || ($col + $cr < 0 || $this->pixelCount <= $col + $cr)){
239
							continue;
240
						}
241
242
						if($this->matrix[$row + $rr][$col + $cr] === $this->matrix[$row][$col]){
243
							$sameCount++;
244
						}
245
					}
246
				}
247
248
				if($sameCount > 5){
249
					$this->lostPoint += (3 + $sameCount - 5);
250
				}
251
252
			}
253
		}
254
255
	}
256
257
	/**
258
	 * @param array $range
259
	 */
260
	protected function testLevel2(array $range){
261
262
		foreach($range as $row){
263
			foreach($range as $col){
264
				$count = 0;
265
266
				if(
267
					   $this->matrix[$row    ][$col    ]
268
					|| $this->matrix[$row    ][$col + 1]
269
					|| $this->matrix[$row + 1][$col    ]
270
					|| $this->matrix[$row + 1][$col + 1]
271
				){
272
					$count++;
273
				}
274
275
				if($count === 0 || $count === 4){
276
					$this->lostPoint += 3;
277
				}
278
279
			}
280
		}
281
282
	}
283
284
	/**
285
	 * @param array $range1
286
	 * @param array $range2
287
	 */
288
	protected function testLevel3(array $range1, array $range2){
289
290 View Code Duplication
		foreach($range1 as $row){
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
291
			foreach($range2 as $col){
292
293
				if(
294
					    $this->matrix[$row][$col    ]
295
					&& !$this->matrix[$row][$col + 1]
296
					&&  $this->matrix[$row][$col + 2]
297
					&&  $this->matrix[$row][$col + 3]
298
					&&  $this->matrix[$row][$col + 4]
299
					&& !$this->matrix[$row][$col + 5]
300
					&&  $this->matrix[$row][$col + 6]
301
				){
302
					$this->lostPoint += 40;
303
				}
304
305
			}
306
		}
307
308 View Code Duplication
		foreach($range1 as $col){
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
309
			foreach($range2 as $row){
310
311
				if(
312
					    $this->matrix[$row    ][$col]
313
					&& !$this->matrix[$row + 1][$col]
314
					&&  $this->matrix[$row + 2][$col]
315
					&&  $this->matrix[$row + 3][$col]
316
					&&  $this->matrix[$row + 4][$col]
317
					&& !$this->matrix[$row + 5][$col]
318
					&&  $this->matrix[$row + 6][$col]
319
				){
320
					$this->lostPoint += 40;
321
				}
322
323
			}
324
		}
325
326
	}
327
328
	/**
329
	 * @param array $range
330
	 */
331
	protected function testLevel4(array $range){
332
333
		foreach($range as $col){
334
			foreach($range as $row){
335
				if($this->matrix[$row][$col]){
336
					$this->darkCount++;
337
				}
338
			}
339
		}
340
341
	}
342
343
	/**
344
	 * @param int $pattern
345
	 */
346
	protected function testPattern($pattern){
347
		$this->getMatrix(true, $pattern);
348
		$this->lostPoint = 0;
349
		$this->darkCount = 0;
350
351
		$range1 = range(0, $this->pixelCount-1);
352
		$range2 = range(0, $this->pixelCount-2);
353
		$range3 = range(0, $this->pixelCount-7);
354
355
		$this->testLevel1($range1);
356
		$this->testLevel2($range2);
357
		$this->testLevel3($range1, $range3);
358
		$this->testLevel4($range1);
359
360
		$this->lostPoint += (abs(100 * $this->darkCount / $this->pixelCount / $this->pixelCount - 50) / 5) * 10;
361
362
		if($pattern === 0 || $this->minLostPoint > $this->lostPoint){
363
			$this->minLostPoint = $this->lostPoint;
0 ignored issues
show
Documentation Bug introduced by
The property $minLostPoint was declared of type integer, but $this->lostPoint is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
364
			$this->maskPattern = $pattern;
365
		}
366
367
	}
368
369
	/**
370
	 * @param bool $test
371
	 */
372
	protected function setTypeNumber($test){
373
		$bits = Util::getBCHTypeNumber($this->typeNumber);
374
		$i = 0;
375
		while($i < 18){
376
			$a = (int)floor($i / 3);
377
			$b = $i % 3 + $this->pixelCount - 8 - 3;
378
379
			$this->matrix[$a][$b] = $this->matrix[$b][$a] = !$test && (($bits >> $i) & 1) === 1;
380
			$i++;
381
		}
382
383
	}
384
385
	/**
386
	 * @param bool $test
387
	 * @param int  $pattern
388
	 */
389
	protected function setTypeInfo($test, $pattern){
390
		$this->setPattern();
391
		$bits = Util::getBCHTypeInfo(($this->errorCorrectLevel << 3) | $pattern);
392
		$i = 0;
393
		while($i < 15){
394
			$mod = !$test && (($bits >> $i) & 1) === 1;
395
396
			switch(true){
397
				case $i < 6: $this->matrix[$i    ][8] = $mod; break;
2 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
398
				case $i < 8: $this->matrix[$i + 1][8] = $mod; break;
2 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
399
				default:
400
					$this->matrix[$this->pixelCount - 15 + $i][8] = $mod;
401
			}
402
403
			switch(true){
404 View Code Duplication
				case $i < 8: $this->matrix[8][$this->pixelCount - $i - 1] = $mod; break;
2 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
405 View Code Duplication
				case $i < 9: $this->matrix[8][           15 + 1 - $i - 1] = $mod; break;
2 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
406
				default:
407
					$this->matrix[8][15 - $i - 1] = $mod;
408
			}
409
			$i++;
410
		}
411
412
		$this->matrix[$this->pixelCount - 8][8] = !$test;
413
	}
414
415
	/**
416
	 * @throws \chillerlan\QRCode\QRCodeException
417
	 */
418
	protected function createData(){
419
		$this->bitBuffer->clear();
420
		$this->bitBuffer->put($this->qrDataInterface->mode, 4);
0 ignored issues
show
Bug introduced by
Accessing mode on the interface chillerlan\QRCode\Data\QRDataInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
421
		$this->bitBuffer->put(
422
			$this->qrDataInterface->mode === QRConst::MODE_KANJI
0 ignored issues
show
Bug introduced by
Accessing mode on the interface chillerlan\QRCode\Data\QRDataInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
423
				? floor($this->qrDataInterface->dataLength / 2)
0 ignored issues
show
Bug introduced by
Accessing dataLength on the interface chillerlan\QRCode\Data\QRDataInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
424
				: $this->qrDataInterface->dataLength,
0 ignored issues
show
Bug introduced by
Accessing dataLength on the interface chillerlan\QRCode\Data\QRDataInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
425
			$this->qrDataInterface->getLengthInBits($this->typeNumber)
426
		);
427
428
		$this->qrDataInterface->write($this->bitBuffer);
429
430
		$MAX_BITS = QRConst::MAX_BITS[$this->typeNumber][$this->errorCorrectLevel];
431
432
		if($this->bitBuffer->length > $MAX_BITS){
433
			throw new QRCodeException('code length overflow. ('.$this->bitBuffer->length.' > '.$MAX_BITS.'bit)');
434
		}
435
436
		// end code.
437
		if($this->bitBuffer->length + 4 <= $MAX_BITS){
438
			$this->bitBuffer->put(0, 4);
439
		}
440
441
		// padding
442
		while($this->bitBuffer->length % 8 !== 0){
443
			$this->bitBuffer->putBit(false);
444
		}
445
446
		// padding
447
		while(true){
448
449
			if($this->bitBuffer->length >= $MAX_BITS){
450
				break;
451
			}
452
453
			$this->bitBuffer->put(QRConst::PAD0, 8);
454
455
			if($this->bitBuffer->length >= $MAX_BITS){
456
				break;
457
			}
458
459
			$this->bitBuffer->put(QRConst::PAD1, 8);
460
		}
461
462
	}
463
464
	/**
465
	 * @return array
466
	 * @throws \chillerlan\QRCode\QRCodeException
467
	 */
468
	protected function createBytes(){
469
		$totalCodeCount = $maxDcCount = $maxEcCount = $offset = $index = 0;
470
		$rsBlocks = Util::getRSBlocks($this->typeNumber, $this->errorCorrectLevel);
471
		$rsBlockCount = count($rsBlocks);
472
		$dcdata = $ecdata = array_fill(0, $rsBlockCount, null);
473
474
		foreach($rsBlocks as $key => $value){
475
			$rsBlockTotal = $value[0];
476
			$rsBlockDataCount = $value[1];
477
478
			$maxDcCount = max($maxDcCount, $rsBlockDataCount);
479
			$maxEcCount = max($maxEcCount, $rsBlockTotal - $rsBlockDataCount);
480
481
			$dcdata[$key] = array_fill(0, $rsBlockDataCount, null);
482
483
			foreach($dcdata[$key] as $i => &$_dcdata){
484
				$bdata = $this->bitBuffer->buffer;
485
				$_dcdata = 0xff & $bdata[$i + $offset];
486
			}
487
488
			$offset += $rsBlockDataCount;
489
490
			$rsPoly = new Polynomial;
491
			$modPoly = new Polynomial;
492
493
			$i = 0;
494
			while($i < $rsBlockTotal - $rsBlockDataCount){
495
				$modPoly->setNum([1, $modPoly->gexp($i)]);
496
				$rsPoly->multiply($modPoly->num);
497
				$i++;
498
			}
499
500
			$rsPolyCount = count($rsPoly->num);
501
			$modPoly->setNum($dcdata[$key], $rsPolyCount - 1)->mod($rsPoly->num);
502
			$ecdata[$key] = array_fill(0, $rsPolyCount - 1, null);
503
			$add = count($modPoly->num) - count($ecdata[$key]);
504
505
			foreach($ecdata[$key] as $i => &$_ecdata){
506
				$modIndex = $i + $add;
507
				$_ecdata = $modIndex >= 0 ? $modPoly->num[$modIndex] : 0;
508
			}
509
510
			$totalCodeCount += $rsBlockTotal;
511
		}
512
513
		$data = array_fill(0, $totalCodeCount, null);
514
		$rsrange = range(0, $rsBlockCount - 1);
515
516
		$i = 0;
517 View Code Duplication
		while($i < $maxDcCount){
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
518
			foreach($rsrange as $key){
519
				if($i < count($dcdata[$key])){
520
					$data[$index++] = $dcdata[$key][$i];
521
				}
522
			}
523
			$i++;
524
		}
525
526
		$i = 0;
527 View Code Duplication
		while($i < $maxEcCount){
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
528
			foreach($rsrange as $key){
529
				if($i < count($ecdata[$key])){
530
					$data[$index++] = $ecdata[$key][$i];
531
				}
532
			}
533
			$i++;
534
		}
535
536
		return $data;
537
	}
538
539
	/**
540
	 * @param int $pattern
541
	 *
542
	 * @throws \chillerlan\QRCode\QRCodeException
543
	 */
544
	protected function mapData($pattern){
545
		$this->createData();
546
		$data = $this->createBytes();
547
		$inc = -1;
548
		$row = $this->pixelCount - 1;
549
		$bitIndex = 7;
550
		$byteIndex = 0;
551
		$dataCount = count($data);
552
553
		for($col = $this->pixelCount - 1; $col > 0; $col -= 2){
554
			if($col === 6){
555
				$col--;
556
			}
557
558
			while(true){
559
				$c = 0;
560
				while($c < 2){
561
					$_col = $col - $c;
562
563
					if($this->matrix[$row][$_col] === null){
564
						$dark = false;
565
566
						if($byteIndex < $dataCount){
567
							$dark = (($data[$byteIndex] >> $bitIndex) & 1) === 1;
568
						}
569
570
						$a = $row + $_col;
571
						$m = $row * $_col;
572
						$MASK_PATTERN = [
573
							QRConst::MASK_PATTERN000 => $a % 2,
574
							QRConst::MASK_PATTERN001 => $row % 2,
575
							QRConst::MASK_PATTERN010 => $_col % 3,
576
							QRConst::MASK_PATTERN011 => $a % 3,
577
							QRConst::MASK_PATTERN100 => (floor($row / 2) + floor($_col / 3)) % 2,
578
							QRConst::MASK_PATTERN101 => $m % 2 + $m % 3,
579
							QRConst::MASK_PATTERN110 => ($m % 2 + $m % 3) % 2,
580
							QRConst::MASK_PATTERN111 => ($m % 3 + $a % 2) % 2,
581
						][$pattern];
582
583
						if($MASK_PATTERN === 0){
584
							$dark = !$dark;
585
						}
586
587
						$this->matrix[$row][$_col] = $dark;
588
589
						$bitIndex--;
590
						if($bitIndex === -1){
591
							$byteIndex++;
592
							$bitIndex = 7;
593
						}
594
					}
595
					$c++;
596
				}
597
598
				$row += $inc;
599
				if($row < 0 || $this->pixelCount <= $row){
600
					$row -= $inc;
601
					$inc = -$inc;
602
					break;
603
				}
604
			}
605
		}
606
607
	}
608
609
	/**
610
	 *
611
	 */
612
	protected function setupPositionProbePattern(){
613
		$range = range(-1, 7);
614
615
		foreach([[0, 0], [$this->pixelCount - 7, 0], [0, $this->pixelCount - 7]] as $grid){
616
			$row = $grid[0];
617
			$col = $grid[1];
618
619
			$r = -1;
620
			while($r < 8){
621
				foreach($range as $c){
622
623
					if($row + $r <= -1 || $this->pixelCount <= $row + $r || $col + $c <= -1 || $this->pixelCount <= $col + $c){
624
						continue;
625
					}
626
627
					$this->matrix[$row + $r][$col + $c] =
628
						(0 <= $r && $r <= 6 && ($c === 0 || $c === 6))
629
						|| (0 <= $c && $c <= 6 && ($r === 0 || $r === 6))
630
						|| (2 <= $c && $c <= 4 &&  2 <= $r && $r <= 4);
631
				}
632
				$r++;
633
			}
634
		}
635
636
	}
637
638
	/**
639
	 *
640
	 */
641
	protected function setupPositionAdjustPattern(){
642
		$range = QRConst::PATTERN_POSITION[$this->typeNumber - 1];
643
644
		foreach($range as $i => $posI){
645
			foreach($range as $j => $posJ){
646
				if($this->matrix[$posI][$posJ] !== null){
647
					continue;
648
				}
649
650
				$row = $col = -2;
651
				while($row < 3){
652
					while($col < 3){
653
						$this->matrix[$posI + $row][$posJ + $col] =
654
							$row === -2 || $row ===  2 || $col === -2 || $col ===  2 ||($row ===  0 && $col === 0);
655
						$col++;
656
					}
657
					$row++;
658
				}
659
			}
660
		}
661
662
	}
663
664
	/**
665
	 * @throws \chillerlan\QRCode\QRCodeException
666
	 */
667
	protected function setPattern(){
668
		$this->setupPositionProbePattern();
669
		$this->setupPositionAdjustPattern();
670
671
		// setupTimingPattern
672
		for($i = 8; $i < $this->pixelCount - 8; $i++){
673
			if($this->matrix[$i][6] !== null){
674
				continue; // @codeCoverageIgnore
675
			}
676
677
			$this->matrix[$i][6] = $this->matrix[6][$i] = $i % 2 === 0;
678
		}
679
680
	}
681
682
	/**
683
	 * @param bool $test
684
	 * @param int  $maskPattern
685
	 *
686
	 * @throws \chillerlan\QRCode\QRCodeException
687
	 */
688
	protected function getMatrix($test, $maskPattern){
689
		$this->pixelCount = $this->typeNumber * 4 + 17;
690
		$this->matrix = array_fill(0, $this->pixelCount, array_fill(0, $this->pixelCount, null));
691
		$this->setTypeInfo($test, $maskPattern);
692
693
		if($this->typeNumber >= 7){
694
			$this->setTypeNumber($test);
695
		}
696
697
		$this->mapData($maskPattern);
698
	}
699
700
}
701