1 | <?php |
||||||
2 | /** |
||||||
3 | * Class QRMatrix |
||||||
4 | * |
||||||
5 | * @created 15.11.2017 |
||||||
6 | * @author Smiley <[email protected]> |
||||||
7 | * @copyright 2017 Smiley |
||||||
8 | * @license MIT |
||||||
9 | */ |
||||||
10 | |||||||
11 | namespace chillerlan\QRCode\Data; |
||||||
12 | |||||||
13 | use chillerlan\QRCode\Common\{BitBuffer, EccLevel, MaskPattern, ReedSolomonEncoder, Version}; |
||||||
14 | use function array_fill, array_map, array_reverse, count, floor; |
||||||
15 | |||||||
16 | /** |
||||||
17 | * Holds an array representation of the final QR Code that contains numerical values for later output modifications; |
||||||
18 | * maps the ECC coded binary data and applies the mask pattern |
||||||
19 | * |
||||||
20 | * @see http://www.thonky.com/qr-code-tutorial/format-version-information |
||||||
21 | */ |
||||||
22 | class QRMatrix{ |
||||||
23 | |||||||
24 | /** @var int */ |
||||||
25 | public const IS_DARK = 0b100000000000; |
||||||
26 | /** @var int */ |
||||||
27 | public const M_NULL = 0b000000000000; |
||||||
28 | /** @var int */ |
||||||
29 | public const M_DARKMODULE = 0b100000000001; |
||||||
30 | /** @var int */ |
||||||
31 | public const M_DATA = 0b000000000010; |
||||||
32 | /** @var int */ |
||||||
33 | public const M_DATA_DARK = 0b100000000010; |
||||||
34 | /** @var int */ |
||||||
35 | public const M_FINDER = 0b000000000100; |
||||||
36 | /** @var int */ |
||||||
37 | public const M_FINDER_DARK = 0b100000000100; |
||||||
38 | /** @var int */ |
||||||
39 | public const M_SEPARATOR = 0b000000001000; |
||||||
40 | /** @var int */ |
||||||
41 | public const M_ALIGNMENT = 0b000000010000; |
||||||
42 | /** @var int */ |
||||||
43 | public const M_ALIGNMENT_DARK = 0b100000010000; |
||||||
44 | /** @var int */ |
||||||
45 | public const M_TIMING = 0b000000100000; |
||||||
46 | /** @var int */ |
||||||
47 | public const M_TIMING_DARK = 0b100000100000; |
||||||
48 | /** @var int */ |
||||||
49 | public const M_FORMAT = 0b000001000000; |
||||||
50 | /** @var int */ |
||||||
51 | public const M_FORMAT_DARK = 0b100001000000; |
||||||
52 | /** @var int */ |
||||||
53 | public const M_VERSION = 0b000010000000; |
||||||
54 | /** @var int */ |
||||||
55 | public const M_VERSION_DARK = 0b100010000000; |
||||||
56 | /** @var int */ |
||||||
57 | public const M_QUIETZONE = 0b000100000000; |
||||||
58 | /** @var int */ |
||||||
59 | public const M_QUIETZONE_DARK = 0b100100000000; |
||||||
60 | /** @var int */ |
||||||
61 | public const M_LOGO = 0b001000000000; |
||||||
62 | /** @var int */ |
||||||
63 | public const M_LOGO_DARK = 0b101000000000; |
||||||
64 | /** @var int */ |
||||||
65 | public const M_FINDER_DOT = 0b110000000000; |
||||||
66 | /** @var int */ |
||||||
67 | public const M_TEST = 0b011111111111; |
||||||
68 | /** @var int */ |
||||||
69 | public const M_TEST_DARK = 0b111111111111; |
||||||
70 | |||||||
71 | /** |
||||||
72 | * Map of flag => coord |
||||||
73 | * |
||||||
74 | * @see \chillerlan\QRCode\Data\QRMatrix::checkNeighbours() |
||||||
75 | * |
||||||
76 | * @var array |
||||||
77 | */ |
||||||
78 | protected const neighbours = [ |
||||||
79 | 0b00000001 => [-1, -1], |
||||||
80 | 0b00000010 => [ 0, -1], |
||||||
81 | 0b00000100 => [ 1, -1], |
||||||
82 | 0b00001000 => [ 1, 0], |
||||||
83 | 0b00010000 => [ 1, 1], |
||||||
84 | 0b00100000 => [ 0, 1], |
||||||
85 | 0b01000000 => [-1, 1], |
||||||
86 | 0b10000000 => [-1, 0], |
||||||
87 | ]; |
||||||
88 | |||||||
89 | /** |
||||||
90 | * the matrix version - always set in QRMatrix, may be null in BitMatrix |
||||||
91 | */ |
||||||
92 | protected ?Version $version = null; |
||||||
93 | |||||||
94 | /** |
||||||
95 | * the current ECC level - always set in QRMatrix, may be null in BitMatrix |
||||||
96 | */ |
||||||
97 | protected ?EccLevel $eccLevel = null; |
||||||
98 | |||||||
99 | /** |
||||||
100 | * the mask pattern that was used in the most recent operation, set via: |
||||||
101 | * |
||||||
102 | * - QRMatrix::setFormatInfo() |
||||||
103 | * - QRMatrix::mask() |
||||||
104 | * - BitMatrix::readFormatInformation() |
||||||
105 | */ |
||||||
106 | protected ?MaskPattern $maskPattern = null; |
||||||
107 | |||||||
108 | /** |
||||||
109 | * the size (side length) of the matrix, including quiet zone (if created) |
||||||
110 | */ |
||||||
111 | protected int $moduleCount; |
||||||
112 | |||||||
113 | /** |
||||||
114 | * the actual matrix data array |
||||||
115 | * |
||||||
116 | * @var int[][] |
||||||
117 | */ |
||||||
118 | protected array $matrix; |
||||||
119 | |||||||
120 | /** |
||||||
121 | * QRMatrix constructor. |
||||||
122 | */ |
||||||
123 | public function __construct(Version $version, EccLevel $eccLevel){ |
||||||
124 | $this->version = $version; |
||||||
125 | $this->eccLevel = $eccLevel; |
||||||
126 | $this->moduleCount = $this->version->getDimension(); |
||||||
127 | $this->matrix = $this->createMatrix($this->moduleCount, $this::M_NULL); |
||||||
128 | } |
||||||
129 | |||||||
130 | /** |
||||||
131 | * Creates a 2-dimensional array (square) of the given $size |
||||||
132 | */ |
||||||
133 | protected function createMatrix(int $size, int $value):array{ |
||||||
134 | return array_fill(0, $size, array_fill(0, $size, $value)); |
||||||
135 | } |
||||||
136 | |||||||
137 | /** |
||||||
138 | * shortcut to initialize the functional patterns |
||||||
139 | */ |
||||||
140 | public function initFunctionalPatterns():self{ |
||||||
141 | return $this |
||||||
142 | ->setFinderPattern() |
||||||
143 | ->setSeparators() |
||||||
144 | ->setAlignmentPattern() |
||||||
145 | ->setTimingPattern() |
||||||
146 | ->setDarkModule() |
||||||
147 | ->setVersionNumber() |
||||||
148 | ->setFormatInfo() |
||||||
149 | ; |
||||||
150 | } |
||||||
151 | |||||||
152 | /** |
||||||
153 | * Returns the data matrix, returns a pure boolean representation if $boolean is set to true |
||||||
154 | * |
||||||
155 | * @return int[][]|bool[][] |
||||||
156 | */ |
||||||
157 | public function getMatrix(bool $boolean = null):array{ |
||||||
158 | |||||||
159 | if($boolean !== true){ |
||||||
160 | return $this->matrix; |
||||||
161 | } |
||||||
162 | |||||||
163 | $matrix = []; |
||||||
164 | |||||||
165 | for($y = 0; $y < $this->moduleCount; $y++){ |
||||||
166 | $matrix[$y] = []; |
||||||
167 | |||||||
168 | for($x = 0; $x < $this->moduleCount; $x++){ |
||||||
169 | $matrix[$y][$x] = $this->checkType($x, $y, $this::IS_DARK); |
||||||
170 | } |
||||||
171 | } |
||||||
172 | |||||||
173 | return $matrix; |
||||||
174 | } |
||||||
175 | |||||||
176 | /** |
||||||
177 | * @deprecated 5.0.0 use QRMatrix::getMatrix() instead |
||||||
178 | * @see \chillerlan\QRCode\Data\QRMatrix::getMatrix() |
||||||
179 | * @codeCoverageIgnore |
||||||
180 | */ |
||||||
181 | public function matrix(bool $boolean = null):array{ |
||||||
182 | return $this->getMatrix($boolean); |
||||||
183 | } |
||||||
184 | |||||||
185 | /** |
||||||
186 | * Returns the current version number |
||||||
187 | */ |
||||||
188 | public function getVersion():?Version{ |
||||||
189 | return $this->version; |
||||||
190 | } |
||||||
191 | |||||||
192 | /** |
||||||
193 | * @deprecated 5.0.0 use QRMatrix::getVersion() instead |
||||||
194 | * @see \chillerlan\QRCode\Data\QRMatrix::getVersion() |
||||||
195 | * @codeCoverageIgnore |
||||||
196 | */ |
||||||
197 | public function version():?Version{ |
||||||
198 | return $this->getVersion(); |
||||||
199 | } |
||||||
200 | |||||||
201 | /** |
||||||
202 | * Returns the current ECC level |
||||||
203 | */ |
||||||
204 | public function getEccLevel():?EccLevel{ |
||||||
205 | return $this->eccLevel; |
||||||
206 | } |
||||||
207 | |||||||
208 | /** |
||||||
209 | * @deprecated 5.0.0 use QRMatrix::getEccLevel() instead |
||||||
210 | * @see \chillerlan\QRCode\Data\QRMatrix::getEccLevel() |
||||||
211 | * @codeCoverageIgnore |
||||||
212 | */ |
||||||
213 | public function eccLevel():?EccLevel{ |
||||||
214 | return $this->getEccLevel(); |
||||||
215 | } |
||||||
216 | |||||||
217 | /** |
||||||
218 | * Returns the current mask pattern |
||||||
219 | */ |
||||||
220 | public function getMaskPattern():?MaskPattern{ |
||||||
221 | return $this->maskPattern; |
||||||
222 | } |
||||||
223 | |||||||
224 | /** |
||||||
225 | * @deprecated 5.0.0 use QRMatrix::getMaskPattern() instead |
||||||
226 | * @see \chillerlan\QRCode\Data\QRMatrix::getMaskPattern() |
||||||
227 | * @codeCoverageIgnore |
||||||
228 | */ |
||||||
229 | public function maskPattern():?MaskPattern{ |
||||||
230 | return $this->getMaskPattern(); |
||||||
231 | } |
||||||
232 | |||||||
233 | /** |
||||||
234 | * Returns the absoulute size of the matrix, including quiet zone (after setting it). |
||||||
235 | * |
||||||
236 | * size = version * 4 + 17 [ + 2 * quietzone size] |
||||||
237 | */ |
||||||
238 | public function getSize():int{ |
||||||
239 | return $this->moduleCount; |
||||||
240 | } |
||||||
241 | |||||||
242 | /** |
||||||
243 | * @deprecated 5.0.0 use QRMatrix::getSize() instead |
||||||
244 | * @see \chillerlan\QRCode\Data\QRMatrix::getSize() |
||||||
245 | * @codeCoverageIgnore |
||||||
246 | */ |
||||||
247 | public function size():int{ |
||||||
248 | return $this->getSize(); |
||||||
249 | } |
||||||
250 | |||||||
251 | /** |
||||||
252 | * Returns the value of the module at position [$x, $y] or -1 if the coordinate is outside the matrix |
||||||
253 | */ |
||||||
254 | public function get(int $x, int $y):int{ |
||||||
255 | |||||||
256 | if(!isset($this->matrix[$y][$x])){ |
||||||
257 | return -1; |
||||||
258 | } |
||||||
259 | |||||||
260 | return $this->matrix[$y][$x]; |
||||||
261 | } |
||||||
262 | |||||||
263 | /** |
||||||
264 | * Sets the $M_TYPE value for the module at position [$x, $y] |
||||||
265 | * |
||||||
266 | * true => $M_TYPE | 0x800 |
||||||
267 | * false => $M_TYPE |
||||||
268 | */ |
||||||
269 | public function set(int $x, int $y, bool $value, int $M_TYPE):self{ |
||||||
270 | |||||||
271 | if(isset($this->matrix[$y][$x])){ |
||||||
272 | $this->matrix[$y][$x] = (($M_TYPE & ~$this::IS_DARK) | (($value) ? $this::IS_DARK : 0)); |
||||||
273 | } |
||||||
274 | |||||||
275 | return $this; |
||||||
276 | } |
||||||
277 | |||||||
278 | /** |
||||||
279 | * Fills an area of $width * $height, from the given starting point [$startX, $startY] (top left) with $value for $M_TYPE. |
||||||
280 | */ |
||||||
281 | public function setArea(int $startX, int $startY, int $width, int $height, bool $value, int $M_TYPE):self{ |
||||||
282 | |||||||
283 | for($y = $startY; $y < ($startY + $height); $y++){ |
||||||
284 | for($x = $startX; $x < ($startX + $width); $x++){ |
||||||
285 | $this->set($x, $y, $value, $M_TYPE); |
||||||
286 | } |
||||||
287 | } |
||||||
288 | |||||||
289 | return $this; |
||||||
290 | } |
||||||
291 | |||||||
292 | /** |
||||||
293 | * Checks whether the module at ($x, $y) is of the given $M_TYPE |
||||||
294 | * |
||||||
295 | * true => $value & $M_TYPE === $M_TYPE |
||||||
296 | */ |
||||||
297 | public function checkType(int $x, int $y, int $M_TYPE):bool{ |
||||||
298 | $val = $this->get($x, $y); |
||||||
299 | |||||||
300 | if($val === -1){ |
||||||
301 | return false; |
||||||
302 | } |
||||||
303 | |||||||
304 | return ($val & $M_TYPE) === $M_TYPE; |
||||||
305 | } |
||||||
306 | |||||||
307 | /** |
||||||
308 | * checks whether the module at ($x, $y) is in the given array of $M_TYPES, |
||||||
309 | * returns true if a match is found, otherwise false. |
||||||
310 | */ |
||||||
311 | public function checkTypeIn(int $x, int $y, array $M_TYPES):bool{ |
||||||
312 | |||||||
313 | foreach($M_TYPES as $type){ |
||||||
314 | if($this->checkType($x, $y, $type)){ |
||||||
315 | return true; |
||||||
316 | } |
||||||
317 | } |
||||||
318 | |||||||
319 | return false; |
||||||
320 | } |
||||||
321 | |||||||
322 | /** |
||||||
323 | * Checks whether the module at ($x, $y) is true (dark) or false (light) |
||||||
324 | */ |
||||||
325 | public function check(int $x, int $y):bool{ |
||||||
326 | return $this->checkType($x, $y, $this::IS_DARK); |
||||||
327 | } |
||||||
328 | |||||||
329 | /** |
||||||
330 | * Checks the status of the neighbouring modules for the module at ($x, $y) and returns a bitmask with the results. |
||||||
331 | * |
||||||
332 | * The 8 flags of the bitmask represent the status of each of the neighbouring fields, |
||||||
333 | * starting with the lowest bit for top left, going clockwise: |
||||||
334 | * |
||||||
335 | * 1 2 3 |
||||||
336 | * 8 # 4 |
||||||
337 | * 7 6 5 |
||||||
338 | */ |
||||||
339 | public function checkNeighbours(int $x, int $y, int $M_TYPE = null):int{ |
||||||
340 | $bits = 0; |
||||||
341 | |||||||
342 | foreach($this::neighbours as $bit => [$ix, $iy]){ |
||||||
343 | $ix += $x; |
||||||
344 | $iy += $y; |
||||||
345 | |||||||
346 | // $M_TYPE is given, skip if the field is not the same type |
||||||
347 | if($M_TYPE !== null && !$this->checkType($ix, $iy, $M_TYPE)){ |
||||||
348 | continue; |
||||||
349 | } |
||||||
350 | |||||||
351 | if($this->checkType($ix, $iy, $this::IS_DARK)){ |
||||||
352 | $bits |= $bit; |
||||||
353 | } |
||||||
354 | } |
||||||
355 | |||||||
356 | return $bits; |
||||||
357 | } |
||||||
358 | |||||||
359 | /** |
||||||
360 | * Sets the "dark module", that is always on the same position 1x1px away from the bottom left finder |
||||||
361 | * |
||||||
362 | * 4 * version + 9 or moduleCount - 8 |
||||||
363 | */ |
||||||
364 | public function setDarkModule():self{ |
||||||
365 | $this->set(8, ($this->moduleCount - 8), true, $this::M_DARKMODULE); |
||||||
366 | |||||||
367 | return $this; |
||||||
368 | } |
||||||
369 | |||||||
370 | /** |
||||||
371 | * Draws the 7x7 finder patterns in the corners top left/right and bottom left |
||||||
372 | * |
||||||
373 | * ISO/IEC 18004:2000 Section 7.3.2 |
||||||
374 | */ |
||||||
375 | public function setFinderPattern():self{ |
||||||
376 | |||||||
377 | $pos = [ |
||||||
378 | [0, 0], // top left |
||||||
379 | [($this->moduleCount - 7), 0], // top right |
||||||
380 | [0, ($this->moduleCount - 7)], // bottom left |
||||||
381 | ]; |
||||||
382 | |||||||
383 | foreach($pos as $c){ |
||||||
384 | $this |
||||||
385 | ->setArea($c[0], $c[1], 7, 7, true, $this::M_FINDER) |
||||||
386 | ->setArea(($c[0] + 1), ($c[1] + 1), 5, 5, false, $this::M_FINDER) |
||||||
387 | ->setArea(($c[0] + 2), ($c[1] + 2), 3, 3, true, $this::M_FINDER_DOT) |
||||||
388 | ; |
||||||
389 | } |
||||||
390 | |||||||
391 | return $this; |
||||||
392 | } |
||||||
393 | |||||||
394 | /** |
||||||
395 | * Draws the separator lines around the finder patterns |
||||||
396 | * |
||||||
397 | * ISO/IEC 18004:2000 Section 7.3.3 |
||||||
398 | */ |
||||||
399 | public function setSeparators():self{ |
||||||
400 | |||||||
401 | $h = [ |
||||||
402 | [7, 0], |
||||||
403 | [($this->moduleCount - 8), 0], |
||||||
404 | [7, ($this->moduleCount - 8)], |
||||||
405 | ]; |
||||||
406 | |||||||
407 | $v = [ |
||||||
408 | [7, 7], |
||||||
409 | [($this->moduleCount - 1), 7], |
||||||
410 | [7, ($this->moduleCount - 8)], |
||||||
411 | ]; |
||||||
412 | |||||||
413 | for($c = 0; $c < 3; $c++){ |
||||||
414 | for($i = 0; $i < 8; $i++){ |
||||||
415 | $this->set($h[$c][0] , ($h[$c][1] + $i), false, $this::M_SEPARATOR); |
||||||
416 | $this->set(($v[$c][0] - $i), $v[$c][1] , false, $this::M_SEPARATOR); |
||||||
417 | } |
||||||
418 | } |
||||||
419 | |||||||
420 | return $this; |
||||||
421 | } |
||||||
422 | |||||||
423 | |||||||
424 | /** |
||||||
425 | * Draws the 5x5 alignment patterns |
||||||
426 | * |
||||||
427 | * ISO/IEC 18004:2000 Section 7.3.5 |
||||||
428 | */ |
||||||
429 | public function setAlignmentPattern():self{ |
||||||
430 | $alignmentPattern = $this->version->getAlignmentPattern(); |
||||||
0 ignored issues
–
show
|
|||||||
431 | |||||||
432 | foreach($alignmentPattern as $y){ |
||||||
433 | foreach($alignmentPattern as $x){ |
||||||
434 | |||||||
435 | // skip existing patterns |
||||||
436 | if($this->matrix[$y][$x] !== $this::M_NULL){ |
||||||
437 | continue; |
||||||
438 | } |
||||||
439 | |||||||
440 | $this |
||||||
441 | ->setArea(($x - 2), ($y - 2), 5, 5, true, $this::M_ALIGNMENT) |
||||||
442 | ->setArea(($x - 1), ($y - 1), 3, 3, false, $this::M_ALIGNMENT) |
||||||
443 | ->set($x, $y, true, $this::M_ALIGNMENT) |
||||||
444 | ; |
||||||
445 | |||||||
446 | } |
||||||
447 | } |
||||||
448 | |||||||
449 | return $this; |
||||||
450 | } |
||||||
451 | |||||||
452 | |||||||
453 | /** |
||||||
454 | * Draws the timing pattern (h/v checkered line between the finder patterns) |
||||||
455 | * |
||||||
456 | * ISO/IEC 18004:2000 Section 7.3.4 |
||||||
457 | */ |
||||||
458 | public function setTimingPattern():self{ |
||||||
459 | |||||||
460 | for($i = 8; $i < ($this->moduleCount - 8); $i++){ |
||||||
461 | |||||||
462 | if($this->matrix[6][$i] !== $this::M_NULL || $this->matrix[$i][6] !== $this::M_NULL){ |
||||||
463 | continue; |
||||||
464 | } |
||||||
465 | |||||||
466 | $v = ($i % 2) === 0; |
||||||
467 | |||||||
468 | $this->set($i, 6, $v, $this::M_TIMING); // h |
||||||
469 | $this->set(6, $i, $v, $this::M_TIMING); // v |
||||||
470 | } |
||||||
471 | |||||||
472 | return $this; |
||||||
473 | } |
||||||
474 | |||||||
475 | /** |
||||||
476 | * Draws the version information, 2x 3x6 pixel |
||||||
477 | * |
||||||
478 | * ISO/IEC 18004:2000 Section 8.10 |
||||||
479 | */ |
||||||
480 | public function setVersionNumber():self{ |
||||||
481 | $bits = $this->version->getVersionPattern(); |
||||||
482 | |||||||
483 | if($bits !== null){ |
||||||
484 | |||||||
485 | for($i = 0; $i < 18; $i++){ |
||||||
486 | $a = (int)($i / 3); |
||||||
487 | $b = (($i % 3) + ($this->moduleCount - 8 - 3)); |
||||||
488 | $v = (($bits >> $i) & 1) === 1; |
||||||
489 | |||||||
490 | $this->set($b, $a, $v, $this::M_VERSION); // ne |
||||||
491 | $this->set($a, $b, $v, $this::M_VERSION); // sw |
||||||
492 | } |
||||||
493 | |||||||
494 | } |
||||||
495 | |||||||
496 | return $this; |
||||||
497 | } |
||||||
498 | |||||||
499 | /** |
||||||
500 | * Draws the format info along the finder patterns. If no $maskPattern, all format info modules will be set to false. |
||||||
501 | * |
||||||
502 | * ISO/IEC 18004:2000 Section 8.9 |
||||||
503 | */ |
||||||
504 | public function setFormatInfo(MaskPattern $maskPattern = null):self{ |
||||||
505 | $this->maskPattern = $maskPattern; |
||||||
506 | |||||||
507 | $bits = ($this->maskPattern instanceof MaskPattern) |
||||||
508 | ? $this->eccLevel->getformatPattern($this->maskPattern) |
||||||
0 ignored issues
–
show
The method
getformatPattern() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
509 | : 0; // sets all format fields to false (test mode) |
||||||
510 | |||||||
511 | for($i = 0; $i < 15; $i++){ |
||||||
512 | $v = (($bits >> $i) & 1) === 1; |
||||||
513 | |||||||
514 | if($i < 6){ |
||||||
515 | $this->set(8, $i, $v, $this::M_FORMAT); |
||||||
516 | } |
||||||
517 | elseif($i < 8){ |
||||||
518 | $this->set(8, ($i + 1), $v, $this::M_FORMAT); |
||||||
519 | } |
||||||
520 | else{ |
||||||
521 | $this->set(8, ($this->moduleCount - 15 + $i), $v, $this::M_FORMAT); |
||||||
522 | } |
||||||
523 | |||||||
524 | if($i < 8){ |
||||||
525 | $this->set(($this->moduleCount - $i - 1), 8, $v, $this::M_FORMAT); |
||||||
526 | } |
||||||
527 | elseif($i < 9){ |
||||||
528 | $this->set(((15 - $i)), 8, $v, $this::M_FORMAT); |
||||||
529 | } |
||||||
530 | else{ |
||||||
531 | $this->set((15 - $i - 1), 8, $v, $this::M_FORMAT); |
||||||
532 | } |
||||||
533 | |||||||
534 | } |
||||||
535 | |||||||
536 | return $this; |
||||||
537 | } |
||||||
538 | |||||||
539 | /** |
||||||
540 | * Draws the "quiet zone" of $size around the matrix |
||||||
541 | * |
||||||
542 | * ISO/IEC 18004:2000 Section 7.3.7 |
||||||
543 | * |
||||||
544 | * @throws \chillerlan\QRCode\Data\QRCodeDataException |
||||||
545 | */ |
||||||
546 | public function setQuietZone(int $quietZoneSize):self{ |
||||||
547 | |||||||
548 | if($this->matrix[($this->moduleCount - 1)][($this->moduleCount - 1)] === $this::M_NULL){ |
||||||
549 | throw new QRCodeDataException('use only after writing data'); |
||||||
550 | } |
||||||
551 | |||||||
552 | // create a matrix with the new size |
||||||
553 | $newSize = ($this->moduleCount + ($quietZoneSize * 2)); |
||||||
554 | $newMatrix = $this->createMatrix($newSize, $this::M_QUIETZONE); |
||||||
555 | |||||||
556 | // copy over the current matrix |
||||||
557 | for($y = 0; $y < $this->moduleCount; $y++){ |
||||||
558 | for($x = 0; $x < $this->moduleCount; $x++){ |
||||||
559 | $newMatrix[($y + $quietZoneSize)][($x + $quietZoneSize)] = $this->matrix[$y][$x]; |
||||||
560 | } |
||||||
561 | } |
||||||
562 | |||||||
563 | // set the new values |
||||||
564 | $this->moduleCount = $newSize; |
||||||
565 | $this->matrix = $newMatrix; |
||||||
566 | |||||||
567 | return $this; |
||||||
568 | } |
||||||
569 | |||||||
570 | /** |
||||||
571 | * Rotates the matrix by 90 degrees clock wise |
||||||
572 | */ |
||||||
573 | public function rotate90():self{ |
||||||
574 | /** @phan-suppress-next-line PhanParamTooFewInternalUnpack */ |
||||||
575 | $this->matrix = array_map((fn(int ...$a):array => array_reverse($a)), ...$this->matrix); |
||||||
576 | |||||||
577 | return $this; |
||||||
578 | } |
||||||
579 | |||||||
580 | /** |
||||||
581 | * Clears a space of $width * $height in order to add a logo or text. |
||||||
582 | * If no $height is given, the space will be assumed a square of $width. |
||||||
583 | * |
||||||
584 | * Additionally, the logo space can be positioned within the QR Code - respecting the main functional patterns - |
||||||
585 | * using $startX and $startY. If either of these are null, the logo space will be centered in that direction. |
||||||
586 | * ECC level "H" (30%) is required. |
||||||
587 | * |
||||||
588 | * The coordinates of $startX and $startY do not include the quiet zone: |
||||||
589 | * [0, 0] is always the top left module of the top left finder pattern, negative values go into the quiet zone top and left. |
||||||
590 | * |
||||||
591 | * Please note that adding a logo space minimizes the error correction capacity of the QR Code and |
||||||
592 | * created images may become unreadable, especially when printed with a chance to receive damage. |
||||||
593 | * Please test thoroughly before using this feature in production. |
||||||
594 | * |
||||||
595 | * This method should be called from within an output module (after the matrix has been filled with data). |
||||||
596 | * Note that there is no restiction on how many times this method could be called on the same matrix instance. |
||||||
597 | * |
||||||
598 | * @link https://github.com/chillerlan/php-qrcode/issues/52 |
||||||
599 | * |
||||||
600 | * @throws \chillerlan\QRCode\Data\QRCodeDataException |
||||||
601 | */ |
||||||
602 | public function setLogoSpace(int $width, int $height = null, int $startX = null, int $startY = null):self{ |
||||||
603 | |||||||
604 | // for logos, we operate in ECC H (30%) only |
||||||
605 | if($this->eccLevel->getLevel() !== EccLevel::H){ |
||||||
606 | throw new QRCodeDataException('ECC level "H" required to add logo space'); |
||||||
607 | } |
||||||
608 | |||||||
609 | if($height === null){ |
||||||
610 | $height = $width; |
||||||
611 | } |
||||||
612 | |||||||
613 | // if width and height happen to be negative or 0 (default value), just return - nothing to do |
||||||
614 | if($width <= 0 || $height <= 0){ |
||||||
615 | return $this; // @codeCoverageIgnore |
||||||
616 | } |
||||||
617 | |||||||
618 | // $this->moduleCount includes the quiet zone (if created), we need the QR size here |
||||||
619 | $length = $this->version->getDimension(); |
||||||
620 | |||||||
621 | // throw if the size is exceeds the qrcode size |
||||||
622 | if($width > $length || $height > $length){ |
||||||
623 | throw new QRCodeDataException('logo dimensions exceed matrix size'); |
||||||
624 | } |
||||||
625 | |||||||
626 | // we need uneven sizes to center the logo space, adjust if needed |
||||||
627 | if($startX === null && ($width % 2) === 0){ |
||||||
628 | $width++; |
||||||
629 | } |
||||||
630 | |||||||
631 | if($startY === null && ($height % 2) === 0){ |
||||||
632 | $height++; |
||||||
633 | } |
||||||
634 | |||||||
635 | // throw if the logo space exceeds the maximum error correction capacity |
||||||
636 | if(($width * $height) > floor($length * $length * 0.2)){ |
||||||
637 | throw new QRCodeDataException('logo space exceeds the maximum error correction capacity'); |
||||||
638 | } |
||||||
639 | |||||||
640 | // quiet zone size |
||||||
641 | $qz = (($this->moduleCount - $length) / 2); |
||||||
642 | // skip quiet zone and the first 9 rows/columns (finder-, mode-, version- and timing patterns) |
||||||
643 | $start = ($qz + 9); |
||||||
644 | // skip quiet zone |
||||||
645 | $end = ($this->moduleCount - $qz); |
||||||
646 | |||||||
647 | // determine start coordinates |
||||||
648 | $startX = ((($startX !== null) ? $startX : ($length - $width) / 2) + $qz); |
||||||
649 | $startY = ((($startY !== null) ? $startY : ($length - $height) / 2) + $qz); |
||||||
650 | $endX = ($startX + $width); |
||||||
651 | $endY = ($startY + $height); |
||||||
652 | |||||||
653 | // clear the space |
||||||
654 | for($y = $startY; $y < $endY; $y++){ |
||||||
655 | for($x = $startX; $x < $endX; $x++){ |
||||||
656 | // out of bounds, skip |
||||||
657 | if($x < $start || $y < $start ||$x >= $end || $y >= $end){ |
||||||
658 | continue; |
||||||
659 | } |
||||||
660 | |||||||
661 | $this->set($x, $y, false, $this::M_LOGO); |
||||||
662 | } |
||||||
663 | } |
||||||
664 | |||||||
665 | return $this; |
||||||
666 | } |
||||||
667 | |||||||
668 | /** |
||||||
669 | * Maps the interleaved binary $data on the matrix |
||||||
670 | */ |
||||||
671 | public function writeCodewords(BitBuffer $bitBuffer):self{ |
||||||
672 | $data = (new ReedSolomonEncoder($this->version, $this->eccLevel))->interleaveEcBytes($bitBuffer); |
||||||
0 ignored issues
–
show
It seems like
$this->version can also be of type null ; however, parameter $version of chillerlan\QRCode\Common...nEncoder::__construct() does only seem to accept chillerlan\QRCode\Common\Version , 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
![]() It seems like
$this->eccLevel can also be of type null ; however, parameter $eccLevel of chillerlan\QRCode\Common...nEncoder::__construct() does only seem to accept chillerlan\QRCode\Common\EccLevel , 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
![]() |
|||||||
673 | $byteCount = count($data); |
||||||
674 | $iByte = 0; |
||||||
675 | $iBit = 7; |
||||||
676 | $direction = true; |
||||||
677 | |||||||
678 | for($i = ($this->moduleCount - 1); $i > 0; $i -= 2){ |
||||||
679 | |||||||
680 | // skip vertical alignment pattern |
||||||
681 | if($i === 6){ |
||||||
682 | $i--; |
||||||
683 | } |
||||||
684 | |||||||
685 | for($count = 0; $count < $this->moduleCount; $count++){ |
||||||
686 | $y = ($direction) ? ($this->moduleCount - 1 - $count) : $count; |
||||||
687 | |||||||
688 | for($col = 0; $col < 2; $col++){ |
||||||
689 | $x = ($i - $col); |
||||||
690 | |||||||
691 | // skip functional patterns |
||||||
692 | if($this->get($x, $y) !== $this::M_NULL){ |
||||||
693 | continue; |
||||||
694 | } |
||||||
695 | |||||||
696 | $v = $iByte < $byteCount && (($data[$iByte] >> $iBit--) & 1) === 1; |
||||||
697 | |||||||
698 | $this->set($x, $y, $v, $this::M_DATA); |
||||||
699 | |||||||
700 | if($iBit === -1){ |
||||||
701 | $iByte++; |
||||||
702 | $iBit = 7; |
||||||
703 | } |
||||||
704 | } |
||||||
705 | } |
||||||
706 | |||||||
707 | $direction = !$direction; // switch directions |
||||||
0 ignored issues
–
show
|
|||||||
708 | } |
||||||
709 | |||||||
710 | return $this; |
||||||
711 | } |
||||||
712 | |||||||
713 | /** |
||||||
714 | * Applies/reverses the mask pattern |
||||||
715 | * |
||||||
716 | * ISO/IEC 18004:2000 Section 8.8.1 |
||||||
717 | */ |
||||||
718 | public function mask(MaskPattern $maskPattern):self{ |
||||||
719 | $this->maskPattern = $maskPattern; |
||||||
720 | $mask = $this->maskPattern->getMask(); |
||||||
721 | |||||||
722 | foreach($this->matrix as $y => $row){ |
||||||
723 | foreach($row as $x => $val){ |
||||||
724 | // skip non-data modules |
||||||
725 | if(($val & $this::M_DATA) !== $this::M_DATA){ |
||||||
726 | continue; |
||||||
727 | } |
||||||
728 | |||||||
729 | if($mask($x, $y)){ |
||||||
730 | $this->set($x, $y, ($val & $this::IS_DARK) !== $this::IS_DARK, $val); |
||||||
731 | } |
||||||
732 | } |
||||||
733 | } |
||||||
734 | |||||||
735 | return $this; |
||||||
736 | } |
||||||
737 | |||||||
738 | } |
||||||
739 |
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.