From 3fd785654372d493c031d9b541ab33a881023a32 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Fri, 26 Feb 2021 19:16:17 +0300 Subject: * switch to composer for qrcode and otp dependencies * move most OTP-related stuff into userhelper * remove old phpqrcode and otphp libraries --- vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php | 65 ++ vendor/chillerlan/php-qrcode/src/Data/Byte.php | 47 ++ vendor/chillerlan/php-qrcode/src/Data/Kanji.php | 70 ++ .../php-qrcode/src/Data/MaskPatternTester.php | 203 ++++++ vendor/chillerlan/php-qrcode/src/Data/Number.php | 82 +++ .../php-qrcode/src/Data/QRCodeDataException.php | 17 + .../php-qrcode/src/Data/QRDataAbstract.php | 351 ++++++++++ .../php-qrcode/src/Data/QRDataInterface.php | 187 ++++++ vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php | 733 +++++++++++++++++++++ 9 files changed, 1755 insertions(+) create mode 100644 vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/Byte.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/Kanji.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/MaskPatternTester.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/Number.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/QRDataAbstract.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/QRDataInterface.php create mode 100644 vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php (limited to 'vendor/chillerlan/php-qrcode/src/Data') diff --git a/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php b/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php new file mode 100644 index 000000000..c6d34e76f --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php @@ -0,0 +1,65 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\QRCode; + +use function array_search, ord, sprintf; + +/** + * Alphanumeric mode: 0 to 9, A to Z, space, $ % * + - . / : + */ +class AlphaNum extends QRDataAbstract{ + + /** + * @inheritdoc + */ + protected $datamode = QRCode::DATA_ALPHANUM; + + /** + * @inheritdoc + */ + protected $lengthBits = [9, 11, 13]; + + /** + * @inheritdoc + */ + protected function write(string $data):void{ + + for($i = 0; $i + 1 < $this->strlen; $i += 2){ + $this->bitBuffer->put($this->getCharCode($data[$i]) * 45 + $this->getCharCode($data[$i + 1]), 11); + } + + if($i < $this->strlen){ + $this->bitBuffer->put($this->getCharCode($data[$i]), 6); + } + + } + + /** + * @param string $chr + * + * @return int + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + protected function getCharCode(string $chr):int{ + $i = array_search($chr, $this::ALPHANUM_CHAR_MAP); + + if($i !== false){ + return $i; + } + + throw new QRCodeDataException(sprintf('illegal char: "%s" [%d]', $chr, ord($chr))); + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Data/Byte.php b/vendor/chillerlan/php-qrcode/src/Data/Byte.php new file mode 100644 index 000000000..f1955645a --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Data/Byte.php @@ -0,0 +1,47 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\QRCode; + +use function ord; + +/** + * Byte mode, ISO-8859-1 or UTF-8 + */ +class Byte extends QRDataAbstract{ + + /** + * @inheritdoc + */ + protected $datamode = QRCode::DATA_BYTE; + + /** + * @inheritdoc + */ + protected $lengthBits = [8, 16, 16]; + + /** + * @inheritdoc + */ + protected function write(string $data):void{ + $i = 0; + + while($i < $this->strlen){ + $this->bitBuffer->put(ord($data[$i]), 8); + $i++; + } + + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Data/Kanji.php b/vendor/chillerlan/php-qrcode/src/Data/Kanji.php new file mode 100644 index 000000000..ce600d4ff --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Data/Kanji.php @@ -0,0 +1,70 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\QRCode; + +use function mb_strlen, ord, sprintf, strlen; + +/** + * Kanji mode: double-byte characters from the Shift JIS character set + */ +class Kanji extends QRDataAbstract{ + + /** + * @inheritdoc + */ + protected $datamode = QRCode::DATA_KANJI; + + /** + * @inheritdoc + */ + protected $lengthBits = [8, 10, 12]; + + /** + * @inheritdoc + */ + protected function getLength(string $data):int{ + return mb_strlen($data, 'SJIS'); + } + + /** + * @inheritdoc + */ + protected function write(string $data):void{ + $len = strlen($data); + + for($i = 0; $i + 1 < $len; $i += 2){ + $c = ((0xff & ord($data[$i])) << 8) | (0xff & ord($data[$i + 1])); + + if(0x8140 <= $c && $c <= 0x9FFC){ + $c -= 0x8140; + } + elseif(0xE040 <= $c && $c <= 0xEBBF){ + $c -= 0xC140; + } + else{ + throw new QRCodeDataException(sprintf('illegal char at %d [%d]', $i + 1, $c)); + } + + $this->bitBuffer->put((($c >> 8) & 0xff) * 0xC0 + ($c & 0xff), 13); + + } + + if($i < $len){ + throw new QRCodeDataException(sprintf('illegal char at %d', $i + 1)); + } + + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Data/MaskPatternTester.php b/vendor/chillerlan/php-qrcode/src/Data/MaskPatternTester.php new file mode 100644 index 000000000..8dfd24180 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Data/MaskPatternTester.php @@ -0,0 +1,203 @@ + + * @copyright 2017 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use function abs, call_user_func_array; + +/** + * The sole purpose of this class is to receive a QRMatrix object and run the pattern tests on it. + * + * @link http://www.thonky.com/qr-code-tutorial/data-masking + */ +class MaskPatternTester{ + + /** + * @var \chillerlan\QRCode\Data\QRMatrix + */ + protected $matrix; + + /** + * @var int + */ + protected $moduleCount; + + /** + * Receives the matrix an sets the module count + * + * @see \chillerlan\QRCode\QROptions::$maskPattern + * @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern + * @see \chillerlan\QRCode\QRCode::getBestMaskPattern() + * + * @param \chillerlan\QRCode\Data\QRMatrix $matrix + */ + public function __construct(QRMatrix $matrix){ + $this->matrix = $matrix; + $this->moduleCount = $this->matrix->size(); + } + + /** + * Returns the penalty for the given mask pattern + * + * @see \chillerlan\QRCode\QROptions::$maskPattern + * @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern + * @see \chillerlan\QRCode\QRCode::getBestMaskPattern() + * + * @return int + */ + public function testPattern():int{ + $penalty = 0; + + for($level = 1; $level <= 4; $level++){ + $penalty += call_user_func_array([$this, 'testLevel'.$level], [$this->matrix->matrix(true)]); + } + + return (int)$penalty; + } + + /** + * Checks for each group of five or more same-colored modules in a row (or column) + * + * @return int + */ + protected function testLevel1(array $m):int{ + $penalty = 0; + + foreach($m as $y => $row){ + foreach($row as $x => $val){ + $count = 0; + + for($ry = -1; $ry <= 1; $ry++){ + + if($y + $ry < 0 || $this->moduleCount <= $y + $ry){ + continue; + } + + for($rx = -1; $rx <= 1; $rx++){ + + if(($ry === 0 && $rx === 0) || (($x + $rx) < 0 || $this->moduleCount <= ($x + $rx))){ + continue; + } + + if($m[$y + $ry][$x + $rx] === $val){ + $count++; + } + + } + } + + if($count > 5){ + $penalty += (3 + $count - 5); + } + + } + } + + return $penalty; + } + + /** + * Checks for each 2x2 area of same-colored modules in the matrix + * + * @return int + */ + protected function testLevel2(array $m):int{ + $penalty = 0; + + foreach($m as $y => $row){ + + if($y > ($this->moduleCount - 2)){ + break; + } + + foreach($row as $x => $val){ + + if($x > ($this->moduleCount - 2)){ + break; + } + + if( + $val === $m[$y][$x + 1] + && $val === $m[$y + 1][$x] + && $val === $m[$y + 1][$x + 1] + ){ + $penalty++; + } + } + } + + return 3 * $penalty; + } + + /** + * Checks if there are patterns that look similar to the finder patterns (1:1:3:1:1 ratio) + * + * @return int + */ + protected function testLevel3(array $m):int{ + $penalties = 0; + + foreach($m as $y => $row){ + foreach($row as $x => $val){ + + if( + ($x + 6) < $this->moduleCount + && $val + && !$m[$y][$x + 1] + && $m[$y][$x + 2] + && $m[$y][$x + 3] + && $m[$y][$x + 4] + && !$m[$y][$x + 5] + && $m[$y][$x + 6] + ){ + $penalties++; + } + + if( + ($y + 6) < $this->moduleCount + && $val + && !$m[$y + 1][$x] + && $m[$y + 2][$x] + && $m[$y + 3][$x] + && $m[$y + 4][$x] + && !$m[$y + 5][$x] + && $m[$y + 6][$x] + ){ + $penalties++; + } + + } + } + + return $penalties * 40; + } + + /** + * Checks if more than half of the modules are dark or light, with a larger penalty for a larger difference + * + * @return float + */ + protected function testLevel4(array $m):float{ + $count = 0; + + foreach($m as $y => $row){ + foreach($row as $x => $val){ + if($val){ + $count++; + } + } + } + + return (abs(100 * $count / $this->moduleCount / $this->moduleCount - 50) / 5) * 10; + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Data/Number.php b/vendor/chillerlan/php-qrcode/src/Data/Number.php new file mode 100644 index 000000000..3936d12c3 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Data/Number.php @@ -0,0 +1,82 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\QRCode; + +use function ord, sprintf, substr; + +/** + * Numeric mode: decimal digits 0 through 9 + */ +class Number extends QRDataAbstract{ + + /** + * @inheritdoc + */ + protected $datamode = QRCode::DATA_NUMBER; + + /** + * @inheritdoc + */ + protected $lengthBits = [10, 12, 14]; + + /** + * @inheritdoc + */ + protected function write(string $data):void{ + $i = 0; + + while($i + 2 < $this->strlen){ + $this->bitBuffer->put($this->parseInt(substr($data, $i, 3)), 10); + $i += 3; + } + + if($i < $this->strlen){ + + if($this->strlen - $i === 1){ + $this->bitBuffer->put($this->parseInt(substr($data, $i, $i + 1)), 4); + } + elseif($this->strlen - $i === 2){ + $this->bitBuffer->put($this->parseInt(substr($data, $i, $i + 2)), 7); + } + + } + + } + + /** + * @param string $string + * + * @return int + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + protected function parseInt(string $string):int{ + $num = 0; + + $len = strlen($string); + for($i = 0; $i < $len; $i++){ + $c = ord($string[$i]); + + if(!in_array($string[$i], $this::NUMBER_CHAR_MAP, true)){ + throw new QRCodeDataException(sprintf('illegal char: "%s" [%d]', $string[$i], $c)); + } + + $c = $c - 48; // ord('0') + $num = $num * 10 + $c; + } + + return $num; + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php b/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php new file mode 100644 index 000000000..862f57ba0 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php @@ -0,0 +1,17 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\QRCodeException; + +class QRCodeDataException extends QRCodeException{} diff --git a/vendor/chillerlan/php-qrcode/src/Data/QRDataAbstract.php b/vendor/chillerlan/php-qrcode/src/Data/QRDataAbstract.php new file mode 100644 index 000000000..f52767e38 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Data/QRDataAbstract.php @@ -0,0 +1,351 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\{QRCode, QRCodeException}; +use chillerlan\QRCode\Helpers\{BitBuffer, Polynomial}; +use chillerlan\Settings\SettingsContainerInterface; + +use function array_fill, array_merge, count, max, mb_convert_encoding, mb_detect_encoding, range, sprintf, strlen; + +/** + * Processes the binary data and maps it on a matrix which is then being returned + */ +abstract class QRDataAbstract implements QRDataInterface{ + + /** + * the string byte count + * + * @var int + */ + protected $strlen; + + /** + * the current data mode: Num, Alphanum, Kanji, Byte + * + * @var int + */ + protected $datamode; + + /** + * mode length bits for the version breakpoints 1-9, 10-26 and 27-40 + * + * @var array + */ + protected $lengthBits = [0, 0, 0]; + + /** + * current QR Code version + * + * @var int + */ + protected $version; + + /** + * the raw data that's being passed to QRMatrix::mapData() + * + * @var array + */ + protected $matrixdata; + + /** + * ECC temp data + * + * @var array + */ + protected $ecdata; + + /** + * ECC temp data + * + * @var array + */ + protected $dcdata; + + /** + * @var \chillerlan\QRCode\QROptions + */ + protected $options; + + /** + * @var \chillerlan\QRCode\Helpers\BitBuffer + */ + protected $bitBuffer; + + /** + * QRDataInterface constructor. + * + * @param \chillerlan\Settings\SettingsContainerInterface $options + * @param string|null $data + */ + public function __construct(SettingsContainerInterface $options, string $data = null){ + $this->options = $options; + + if($data !== null){ + $this->setData($data); + } + } + + /** + * @inheritDoc + */ + public function setData(string $data):QRDataInterface{ + + if($this->datamode === QRCode::DATA_KANJI){ + $data = mb_convert_encoding($data, 'SJIS', mb_detect_encoding($data)); + } + + $this->strlen = $this->getLength($data); + $this->version = $this->options->version === QRCode::VERSION_AUTO + ? $this->getMinimumVersion() + : $this->options->version; + + $this->matrixdata = $this + ->writeBitBuffer($data) + ->maskECC() + ; + + return $this; + } + + /** + * @inheritDoc + */ + public function initMatrix(int $maskPattern, bool $test = null):QRMatrix{ + return (new QRMatrix($this->version, $this->options->eccLevel)) + ->setFinderPattern() + ->setSeparators() + ->setAlignmentPattern() + ->setTimingPattern() + ->setVersionNumber($test) + ->setFormatInfo($maskPattern, $test) + ->setDarkModule() + ->mapData($this->matrixdata, $maskPattern) + ; + } + + /** + * returns the length bits for the version breakpoints 1-9, 10-26 and 27-40 + * + * @return int + * @throws \chillerlan\QRCode\Data\QRCodeDataException + * @codeCoverageIgnore + */ + protected function getLengthBits():int{ + + foreach([9, 26, 40] as $key => $breakpoint){ + if($this->version <= $breakpoint){ + return $this->lengthBits[$key]; + } + } + + throw new QRCodeDataException(sprintf('invalid version number: %d', $this->version)); + } + + /** + * returns the byte count of the $data string + * + * @param string $data + * + * @return int + */ + protected function getLength(string $data):int{ + return strlen($data); + } + + /** + * returns the minimum version number for the given string + * + * @return int + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + protected function getMinimumVersion():int{ + $maxlength = 0; + + // guess the version number within the given range + foreach(range($this->options->versionMin, $this->options->versionMax) as $version){ + $maxlength = $this::MAX_LENGTH[$version][QRCode::DATA_MODES[$this->datamode]][QRCode::ECC_MODES[$this->options->eccLevel]]; + + if($this->strlen <= $maxlength){ + return $version; + } + } + + throw new QRCodeDataException(sprintf('data exceeds %d characters', $maxlength)); + } + + /** + * writes the actual data string to the BitBuffer + * + * @see \chillerlan\QRCode\Data\QRDataAbstract::writeBitBuffer() + * + * @param string $data + * + * @return void + */ + abstract protected function write(string $data):void; + + /** + * creates a BitBuffer and writes the string data to it + * + * @param string $data + * + * @return \chillerlan\QRCode\Data\QRDataAbstract + * @throws \chillerlan\QRCode\QRCodeException + */ + protected function writeBitBuffer(string $data):QRDataInterface{ + $this->bitBuffer = new BitBuffer; + + $MAX_BITS = $this::MAX_BITS[$this->version][QRCode::ECC_MODES[$this->options->eccLevel]]; + + $this->bitBuffer + ->clear() + ->put($this->datamode, 4) + ->put($this->strlen, $this->getLengthBits()) + ; + + $this->write($data); + + // there was an error writing the BitBuffer data, which is... unlikely. + if($this->bitBuffer->length > $MAX_BITS){ + throw new QRCodeException(sprintf('code length overflow. (%d > %d bit)', $this->bitBuffer->length, $MAX_BITS)); // @codeCoverageIgnore + } + + // end code. + if($this->bitBuffer->length + 4 <= $MAX_BITS){ + $this->bitBuffer->put(0, 4); + } + + // padding + while($this->bitBuffer->length % 8 !== 0){ + $this->bitBuffer->putBit(false); + } + + // padding + while(true){ + + if($this->bitBuffer->length >= $MAX_BITS){ + break; + } + + $this->bitBuffer->put(0xEC, 8); + + if($this->bitBuffer->length >= $MAX_BITS){ + break; + } + + $this->bitBuffer->put(0x11, 8); + } + + return $this; + } + + /** + * ECC masking + * + * @link http://www.thonky.com/qr-code-tutorial/error-correction-coding + * + * @return array + */ + protected function maskECC():array{ + [$l1, $l2, $b1, $b2] = $this::RSBLOCKS[$this->version][QRCode::ECC_MODES[$this->options->eccLevel]]; + + $rsBlocks = array_fill(0, $l1, [$b1, $b2]); + $rsCount = $l1 + $l2; + $this->ecdata = array_fill(0, $rsCount, null); + $this->dcdata = $this->ecdata; + + if($l2 > 0){ + $rsBlocks = array_merge($rsBlocks, array_fill(0, $l2, [$b1 + 1, $b2 + 1])); + } + + $totalCodeCount = 0; + $maxDcCount = 0; + $maxEcCount = 0; + $offset = 0; + + foreach($rsBlocks as $key => $block){ + [$rsBlockTotal, $dcCount] = $block; + + $ecCount = $rsBlockTotal - $dcCount; + $maxDcCount = max($maxDcCount, $dcCount); + $maxEcCount = max($maxEcCount, $ecCount); + $this->dcdata[$key] = array_fill(0, $dcCount, null); + + foreach($this->dcdata[$key] as $a => $_z){ + $this->dcdata[$key][$a] = 0xff & $this->bitBuffer->buffer[$a + $offset]; + } + + [$num, $add] = $this->poly($key, $ecCount); + + foreach($this->ecdata[$key] as $c => $_z){ + $modIndex = $c + $add; + $this->ecdata[$key][$c] = $modIndex >= 0 ? $num[$modIndex] : 0; + } + + $offset += $dcCount; + $totalCodeCount += $rsBlockTotal; + } + + $data = array_fill(0, $totalCodeCount, null); + $index = 0; + + $mask = function($arr, $count) use (&$data, &$index, $rsCount){ + for($x = 0; $x < $count; $x++){ + for($y = 0; $y < $rsCount; $y++){ + if($x < count($arr[$y])){ + $data[$index] = $arr[$y][$x]; + $index++; + } + } + } + }; + + $mask($this->dcdata, $maxDcCount); + $mask($this->ecdata, $maxEcCount); + + return $data; + } + + /** + * @param int $key + * @param int $count + * + * @return int[] + */ + protected function poly(int $key, int $count):array{ + $rsPoly = new Polynomial; + $modPoly = new Polynomial; + + for($i = 0; $i < $count; $i++){ + $modPoly->setNum([1, $modPoly->gexp($i)]); + $rsPoly->multiply($modPoly->getNum()); + } + + $rsPolyCount = count($rsPoly->getNum()); + + $modPoly + ->setNum($this->dcdata[$key], $rsPolyCount - 1) + ->mod($rsPoly->getNum()) + ; + + $this->ecdata[$key] = array_fill(0, $rsPolyCount - 1, null); + $num = $modPoly->getNum(); + + return [ + $num, + count($num) - count($this->ecdata[$key]), + ]; + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Data/QRDataInterface.php b/vendor/chillerlan/php-qrcode/src/Data/QRDataInterface.php new file mode 100644 index 000000000..653386222 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Data/QRDataInterface.php @@ -0,0 +1,187 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +/** + * + */ +interface QRDataInterface{ + + const NUMBER_CHAR_MAP = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + + const ALPHANUM_CHAR_MAP = [ + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', ' ', '$', '%', '*', + '+', '-', '.', '/', ':', + ]; + + /** + * @link http://www.qrcode.com/en/about/version.html + */ + const MAX_LENGTH =[ + // v => [NUMERIC => [L, M, Q, H ], ALPHANUM => [L, M, Q, H], BINARY => [L, M, Q, H ], KANJI => [L, M, Q, H ]] // modules + 1 => [[ 41, 34, 27, 17], [ 25, 20, 16, 10], [ 17, 14, 11, 7], [ 10, 8, 7, 4]], // 21 + 2 => [[ 77, 63, 48, 34], [ 47, 38, 29, 20], [ 32, 26, 20, 14], [ 20, 16, 12, 8]], // 25 + 3 => [[ 127, 101, 77, 58], [ 77, 61, 47, 35], [ 53, 42, 32, 24], [ 32, 26, 20, 15]], // 29 + 4 => [[ 187, 149, 111, 82], [ 114, 90, 67, 50], [ 78, 62, 46, 34], [ 48, 38, 28, 21]], // 33 + 5 => [[ 255, 202, 144, 106], [ 154, 122, 87, 64], [ 106, 84, 60, 44], [ 65, 52, 37, 27]], // 37 + 6 => [[ 322, 255, 178, 139], [ 195, 154, 108, 84], [ 134, 106, 74, 58], [ 82, 65, 45, 36]], // 41 + 7 => [[ 370, 293, 207, 154], [ 224, 178, 125, 93], [ 154, 122, 86, 64], [ 95, 75, 53, 39]], // 45 + 8 => [[ 461, 365, 259, 202], [ 279, 221, 157, 122], [ 192, 152, 108, 84], [ 118, 93, 66, 52]], // 49 + 9 => [[ 552, 432, 312, 235], [ 335, 262, 189, 143], [ 230, 180, 130, 98], [ 141, 111, 80, 60]], // 53 + 10 => [[ 652, 513, 364, 288], [ 395, 311, 221, 174], [ 271, 213, 151, 119], [ 167, 131, 93, 74]], // 57 + 11 => [[ 772, 604, 427, 331], [ 468, 366, 259, 200], [ 321, 251, 177, 137], [ 198, 155, 109, 85]], // 61 + 12 => [[ 883, 691, 489, 374], [ 535, 419, 296, 227], [ 367, 287, 203, 155], [ 226, 177, 125, 96]], // 65 + 13 => [[1022, 796, 580, 427], [ 619, 483, 352, 259], [ 425, 331, 241, 177], [ 262, 204, 149, 109]], // 69 NICE! + 14 => [[1101, 871, 621, 468], [ 667, 528, 376, 283], [ 458, 362, 258, 194], [ 282, 223, 159, 120]], // 73 + 15 => [[1250, 991, 703, 530], [ 758, 600, 426, 321], [ 520, 412, 292, 220], [ 320, 254, 180, 136]], // 77 + 16 => [[1408, 1082, 775, 602], [ 854, 656, 470, 365], [ 586, 450, 322, 250], [ 361, 277, 198, 154]], // 81 + 17 => [[1548, 1212, 876, 674], [ 938, 734, 531, 408], [ 644, 504, 364, 280], [ 397, 310, 224, 173]], // 85 + 18 => [[1725, 1346, 948, 746], [1046, 816, 574, 452], [ 718, 560, 394, 310], [ 442, 345, 243, 191]], // 89 + 19 => [[1903, 1500, 1063, 813], [1153, 909, 644, 493], [ 792, 624, 442, 338], [ 488, 384, 272, 208]], // 93 + 20 => [[2061, 1600, 1159, 919], [1249, 970, 702, 557], [ 858, 666, 482, 382], [ 528, 410, 297, 235]], // 97 + 21 => [[2232, 1708, 1224, 969], [1352, 1035, 742, 587], [ 929, 711, 509, 403], [ 572, 438, 314, 248]], // 101 + 22 => [[2409, 1872, 1358, 1056], [1460, 1134, 823, 640], [1003, 779, 565, 439], [ 618, 480, 348, 270]], // 105 + 23 => [[2620, 2059, 1468, 1108], [1588, 1248, 890, 672], [1091, 857, 611, 461], [ 672, 528, 376, 284]], // 109 + 24 => [[2812, 2188, 1588, 1228], [1704, 1326, 963, 744], [1171, 911, 661, 511], [ 721, 561, 407, 315]], // 113 + 25 => [[3057, 2395, 1718, 1286], [1853, 1451, 1041, 779], [1273, 997, 715, 535], [ 784, 614, 440, 330]], // 117 + 26 => [[3283, 2544, 1804, 1425], [1990, 1542, 1094, 864], [1367, 1059, 751, 593], [ 842, 652, 462, 365]], // 121 + 27 => [[3517, 2701, 1933, 1501], [2132, 1637, 1172, 910], [1465, 1125, 805, 625], [ 902, 692, 496, 385]], // 125 + 28 => [[3669, 2857, 2085, 1581], [2223, 1732, 1263, 958], [1528, 1190, 868, 658], [ 940, 732, 534, 405]], // 129 + 29 => [[3909, 3035, 2181, 1677], [2369, 1839, 1322, 1016], [1628, 1264, 908, 698], [1002, 778, 559, 430]], // 133 + 30 => [[4158, 3289, 2358, 1782], [2520, 1994, 1429, 1080], [1732, 1370, 982, 742], [1066, 843, 604, 457]], // 137 + 31 => [[4417, 3486, 2473, 1897], [2677, 2113, 1499, 1150], [1840, 1452, 1030, 790], [1132, 894, 634, 486]], // 141 + 32 => [[4686, 3693, 2670, 2022], [2840, 2238, 1618, 1226], [1952, 1538, 1112, 842], [1201, 947, 684, 518]], // 145 + 33 => [[4965, 3909, 2805, 2157], [3009, 2369, 1700, 1307], [2068, 1628, 1168, 898], [1273, 1002, 719, 553]], // 149 + 34 => [[5253, 4134, 2949, 2301], [3183, 2506, 1787, 1394], [2188, 1722, 1228, 958], [1347, 1060, 756, 590]], // 153 + 35 => [[5529, 4343, 3081, 2361], [3351, 2632, 1867, 1431], [2303, 1809, 1283, 983], [1417, 1113, 790, 605]], // 157 + 36 => [[5836, 4588, 3244, 2524], [3537, 2780, 1966, 1530], [2431, 1911, 1351, 1051], [1496, 1176, 832, 647]], // 161 + 37 => [[6153, 4775, 3417, 2625], [3729, 2894, 2071, 1591], [2563, 1989, 1423, 1093], [1577, 1224, 876, 673]], // 165 + 38 => [[6479, 5039, 3599, 2735], [3927, 3054, 2181, 1658], [2699, 2099, 1499, 1139], [1661, 1292, 923, 701]], // 169 + 39 => [[6743, 5313, 3791, 2927], [4087, 3220, 2298, 1774], [2809, 2213, 1579, 1219], [1729, 1362, 972, 750]], // 173 + 40 => [[7089, 5596, 3993, 3057], [4296, 3391, 2420, 1852], [2953, 2331, 1663, 1273], [1817, 1435, 1024, 784]], // 177 + ]; + + const MAX_BITS = [ + // version => [L, M, Q, H ] + 1 => [ 152, 128, 104, 72], + 2 => [ 272, 224, 176, 128], + 3 => [ 440, 352, 272, 208], + 4 => [ 640, 512, 384, 288], + 5 => [ 864, 688, 496, 368], + 6 => [ 1088, 864, 608, 480], + 7 => [ 1248, 992, 704, 528], + 8 => [ 1552, 1232, 880, 688], + 9 => [ 1856, 1456, 1056, 800], + 10 => [ 2192, 1728, 1232, 976], + 11 => [ 2592, 2032, 1440, 1120], + 12 => [ 2960, 2320, 1648, 1264], + 13 => [ 3424, 2672, 1952, 1440], + 14 => [ 3688, 2920, 2088, 1576], + 15 => [ 4184, 3320, 2360, 1784], + 16 => [ 4712, 3624, 2600, 2024], + 17 => [ 5176, 4056, 2936, 2264], + 18 => [ 5768, 4504, 3176, 2504], + 19 => [ 6360, 5016, 3560, 2728], + 20 => [ 6888, 5352, 3880, 3080], + 21 => [ 7456, 5712, 4096, 3248], + 22 => [ 8048, 6256, 4544, 3536], + 23 => [ 8752, 6880, 4912, 3712], + 24 => [ 9392, 7312, 5312, 4112], + 25 => [10208, 8000, 5744, 4304], + 26 => [10960, 8496, 6032, 4768], + 27 => [11744, 9024, 6464, 5024], + 28 => [12248, 9544, 6968, 5288], + 29 => [13048, 10136, 7288, 5608], + 30 => [13880, 10984, 7880, 5960], + 31 => [14744, 11640, 8264, 6344], + 32 => [15640, 12328, 8920, 6760], + 33 => [16568, 13048, 9368, 7208], + 34 => [17528, 13800, 9848, 7688], + 35 => [18448, 14496, 10288, 7888], + 36 => [19472, 15312, 10832, 8432], + 37 => [20528, 15936, 11408, 8768], + 38 => [21616, 16816, 12016, 9136], + 39 => [22496, 17728, 12656, 9776], + 40 => [23648, 18672, 13328, 10208], + ]; + + /** + * @link http://www.thonky.com/qr-code-tutorial/error-correction-table + */ + const RSBLOCKS = [ + 1 => [[ 1, 0, 26, 19], [ 1, 0, 26, 16], [ 1, 0, 26, 13], [ 1, 0, 26, 9]], + 2 => [[ 1, 0, 44, 34], [ 1, 0, 44, 28], [ 1, 0, 44, 22], [ 1, 0, 44, 16]], + 3 => [[ 1, 0, 70, 55], [ 1, 0, 70, 44], [ 2, 0, 35, 17], [ 2, 0, 35, 13]], + 4 => [[ 1, 0, 100, 80], [ 2, 0, 50, 32], [ 2, 0, 50, 24], [ 4, 0, 25, 9]], + 5 => [[ 1, 0, 134, 108], [ 2, 0, 67, 43], [ 2, 2, 33, 15], [ 2, 2, 33, 11]], + 6 => [[ 2, 0, 86, 68], [ 4, 0, 43, 27], [ 4, 0, 43, 19], [ 4, 0, 43, 15]], + 7 => [[ 2, 0, 98, 78], [ 4, 0, 49, 31], [ 2, 4, 32, 14], [ 4, 1, 39, 13]], + 8 => [[ 2, 0, 121, 97], [ 2, 2, 60, 38], [ 4, 2, 40, 18], [ 4, 2, 40, 14]], + 9 => [[ 2, 0, 146, 116], [ 3, 2, 58, 36], [ 4, 4, 36, 16], [ 4, 4, 36, 12]], + 10 => [[ 2, 2, 86, 68], [ 4, 1, 69, 43], [ 6, 2, 43, 19], [ 6, 2, 43, 15]], + 11 => [[ 4, 0, 101, 81], [ 1, 4, 80, 50], [ 4, 4, 50, 22], [ 3, 8, 36, 12]], + 12 => [[ 2, 2, 116, 92], [ 6, 2, 58, 36], [ 4, 6, 46, 20], [ 7, 4, 42, 14]], + 13 => [[ 4, 0, 133, 107], [ 8, 1, 59, 37], [ 8, 4, 44, 20], [12, 4, 33, 11]], + 14 => [[ 3, 1, 145, 115], [ 4, 5, 64, 40], [11, 5, 36, 16], [11, 5, 36, 12]], + 15 => [[ 5, 1, 109, 87], [ 5, 5, 65, 41], [ 5, 7, 54, 24], [11, 7, 36, 12]], + 16 => [[ 5, 1, 122, 98], [ 7, 3, 73, 45], [15, 2, 43, 19], [ 3, 13, 45, 15]], + 17 => [[ 1, 5, 135, 107], [10, 1, 74, 46], [ 1, 15, 50, 22], [ 2, 17, 42, 14]], + 18 => [[ 5, 1, 150, 120], [ 9, 4, 69, 43], [17, 1, 50, 22], [ 2, 19, 42, 14]], + 19 => [[ 3, 4, 141, 113], [ 3, 11, 70, 44], [17, 4, 47, 21], [ 9, 16, 39, 13]], + 20 => [[ 3, 5, 135, 107], [ 3, 13, 67, 41], [15, 5, 54, 24], [15, 10, 43, 15]], + 21 => [[ 4, 4, 144, 116], [17, 0, 68, 42], [17, 6, 50, 22], [19, 6, 46, 16]], + 22 => [[ 2, 7, 139, 111], [17, 0, 74, 46], [ 7, 16, 54, 24], [34, 0, 37, 13]], + 23 => [[ 4, 5, 151, 121], [ 4, 14, 75, 47], [11, 14, 54, 24], [16, 14, 45, 15]], + 24 => [[ 6, 4, 147, 117], [ 6, 14, 73, 45], [11, 16, 54, 24], [30, 2, 46, 16]], + 25 => [[ 8, 4, 132, 106], [ 8, 13, 75, 47], [ 7, 22, 54, 24], [22, 13, 45, 15]], + 26 => [[10, 2, 142, 114], [19, 4, 74, 46], [28, 6, 50, 22], [33, 4, 46, 16]], + 27 => [[ 8, 4, 152, 122], [22, 3, 73, 45], [ 8, 26, 53, 23], [12, 28, 45, 15]], + 28 => [[ 3, 10, 147, 117], [ 3, 23, 73, 45], [ 4, 31, 54, 24], [11, 31, 45, 15]], + 29 => [[ 7, 7, 146, 116], [21, 7, 73, 45], [ 1, 37, 53, 23], [19, 26, 45, 15]], + 30 => [[ 5, 10, 145, 115], [19, 10, 75, 47], [15, 25, 54, 24], [23, 25, 45, 15]], + 31 => [[13, 3, 145, 115], [ 2, 29, 74, 46], [42, 1, 54, 24], [23, 28, 45, 15]], + 32 => [[17, 0, 145, 115], [10, 23, 74, 46], [10, 35, 54, 24], [19, 35, 45, 15]], + 33 => [[17, 1, 145, 115], [14, 21, 74, 46], [29, 19, 54, 24], [11, 46, 45, 15]], + 34 => [[13, 6, 145, 115], [14, 23, 74, 46], [44, 7, 54, 24], [59, 1, 46, 16]], + 35 => [[12, 7, 151, 121], [12, 26, 75, 47], [39, 14, 54, 24], [22, 41, 45, 15]], + 36 => [[ 6, 14, 151, 121], [ 6, 34, 75, 47], [46, 10, 54, 24], [ 2, 64, 45, 15]], + 37 => [[17, 4, 152, 122], [29, 14, 74, 46], [49, 10, 54, 24], [24, 46, 45, 15]], + 38 => [[ 4, 18, 152, 122], [13, 32, 74, 46], [48, 14, 54, 24], [42, 32, 45, 15]], + 39 => [[20, 4, 147, 117], [40, 7, 75, 47], [43, 22, 54, 24], [10, 67, 45, 15]], + 40 => [[19, 6, 148, 118], [18, 31, 75, 47], [34, 34, 54, 24], [20, 61, 45, 15]], + ]; + + /** + * Sets the data string (internally called by the constructor) + * + * @param string $data + * + * @return \chillerlan\QRCode\Data\QRDataInterface + */ + public function setData(string $data):QRDataInterface; + + /** + * returns a fresh matrix object with the data written for the given $maskPattern + * + * @param int $maskPattern + * @param bool|null $test + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function initMatrix(int $maskPattern, bool $test = null):QRMatrix; + +} diff --git a/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php b/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php new file mode 100644 index 000000000..a6d8b09e2 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php @@ -0,0 +1,733 @@ + + * @copyright 2017 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\QRCode; +use Closure; + +use function array_fill, array_key_exists, array_push, array_unshift, count, floor, in_array, max, min, range; + +/** + * @link http://www.thonky.com/qr-code-tutorial/format-version-information + */ +class QRMatrix{ + + public const M_NULL = 0x00; + public const M_DARKMODULE = 0x02; + public const M_DATA = 0x04; + public const M_FINDER = 0x06; + public const M_SEPARATOR = 0x08; + public const M_ALIGNMENT = 0x0a; + public const M_TIMING = 0x0c; + public const M_FORMAT = 0x0e; + public const M_VERSION = 0x10; + public const M_QUIETZONE = 0x12; + public const M_LOGO = 0x14; + public const M_FINDER_DOT = 0x16; + + public const M_TEST = 0xff; + + /** + * @link http://www.thonky.com/qr-code-tutorial/alignment-pattern-locations + * + * version -> pattern + */ + protected const alignmentPattern = [ + 1 => [], + 2 => [6, 18], + 3 => [6, 22], + 4 => [6, 26], + 5 => [6, 30], + 6 => [6, 34], + 7 => [6, 22, 38], + 8 => [6, 24, 42], + 9 => [6, 26, 46], + 10 => [6, 28, 50], + 11 => [6, 30, 54], + 12 => [6, 32, 58], + 13 => [6, 34, 62], + 14 => [6, 26, 46, 66], + 15 => [6, 26, 48, 70], + 16 => [6, 26, 50, 74], + 17 => [6, 30, 54, 78], + 18 => [6, 30, 56, 82], + 19 => [6, 30, 58, 86], + 20 => [6, 34, 62, 90], + 21 => [6, 28, 50, 72, 94], + 22 => [6, 26, 50, 74, 98], + 23 => [6, 30, 54, 78, 102], + 24 => [6, 28, 54, 80, 106], + 25 => [6, 32, 58, 84, 110], + 26 => [6, 30, 58, 86, 114], + 27 => [6, 34, 62, 90, 118], + 28 => [6, 26, 50, 74, 98, 122], + 29 => [6, 30, 54, 78, 102, 126], + 30 => [6, 26, 52, 78, 104, 130], + 31 => [6, 30, 56, 82, 108, 134], + 32 => [6, 34, 60, 86, 112, 138], + 33 => [6, 30, 58, 86, 114, 142], + 34 => [6, 34, 62, 90, 118, 146], + 35 => [6, 30, 54, 78, 102, 126, 150], + 36 => [6, 24, 50, 76, 102, 128, 154], + 37 => [6, 28, 54, 80, 106, 132, 158], + 38 => [6, 32, 58, 84, 110, 136, 162], + 39 => [6, 26, 54, 82, 110, 138, 166], + 40 => [6, 30, 58, 86, 114, 142, 170], + ]; + + /** + * @link http://www.thonky.com/qr-code-tutorial/format-version-tables + * + * no version pattern for QR Codes < 7 + */ + protected const versionPattern = [ + 7 => 0b000111110010010100, + 8 => 0b001000010110111100, + 9 => 0b001001101010011001, + 10 => 0b001010010011010011, + 11 => 0b001011101111110110, + 12 => 0b001100011101100010, + 13 => 0b001101100001000111, + 14 => 0b001110011000001101, + 15 => 0b001111100100101000, + 16 => 0b010000101101111000, + 17 => 0b010001010001011101, + 18 => 0b010010101000010111, + 19 => 0b010011010100110010, + 20 => 0b010100100110100110, + 21 => 0b010101011010000011, + 22 => 0b010110100011001001, + 23 => 0b010111011111101100, + 24 => 0b011000111011000100, + 25 => 0b011001000111100001, + 26 => 0b011010111110101011, + 27 => 0b011011000010001110, + 28 => 0b011100110000011010, + 29 => 0b011101001100111111, + 30 => 0b011110110101110101, + 31 => 0b011111001001010000, + 32 => 0b100000100111010101, + 33 => 0b100001011011110000, + 34 => 0b100010100010111010, + 35 => 0b100011011110011111, + 36 => 0b100100101100001011, + 37 => 0b100101010000101110, + 38 => 0b100110101001100100, + 39 => 0b100111010101000001, + 40 => 0b101000110001101001, + ]; + + // ECC level -> mask pattern + protected const formatPattern = [ + [ // L + 0b111011111000100, + 0b111001011110011, + 0b111110110101010, + 0b111100010011101, + 0b110011000101111, + 0b110001100011000, + 0b110110001000001, + 0b110100101110110, + ], + [ // M + 0b101010000010010, + 0b101000100100101, + 0b101111001111100, + 0b101101101001011, + 0b100010111111001, + 0b100000011001110, + 0b100111110010111, + 0b100101010100000, + ], + [ // Q + 0b011010101011111, + 0b011000001101000, + 0b011111100110001, + 0b011101000000110, + 0b010010010110100, + 0b010000110000011, + 0b010111011011010, + 0b010101111101101, + ], + [ // H + 0b001011010001001, + 0b001001110111110, + 0b001110011100111, + 0b001100111010000, + 0b000011101100010, + 0b000001001010101, + 0b000110100001100, + 0b000100000111011, + ], + ]; + + /** + * @var int + */ + protected $version; + + /** + * @var int + */ + protected $eclevel; + + /** + * @var int + */ + protected $maskPattern = QRCode::MASK_PATTERN_AUTO; + + /** + * @var int + */ + protected $moduleCount; + + /** + * @var mixed[] + */ + protected $matrix; + + /** + * QRMatrix constructor. + * + * @param int $version + * @param int $eclevel + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function __construct(int $version, int $eclevel){ + + if(!in_array($version, range(1, 40), true)){ + throw new QRCodeDataException('invalid QR Code version'); + } + + if(!array_key_exists($eclevel, QRCode::ECC_MODES)){ + throw new QRCodeDataException('invalid ecc level'); + } + + $this->version = $version; + $this->eclevel = $eclevel; + $this->moduleCount = $this->version * 4 + 17; + $this->matrix = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, $this::M_NULL)); + } + + /** + * Returns the data matrix, returns a pure boolean representation if $boolean is set to true + * + * @return int[][]|bool[][] + */ + public function matrix(bool $boolean = false):array{ + + if(!$boolean){ + return $this->matrix; + } + + $matrix = []; + + foreach($this->matrix as $y => $row){ + $matrix[$y] = []; + + foreach($row as $x => $val){ + $matrix[$y][$x] = ($val >> 8) > 0; + } + } + + return $matrix; + } + + /** + * @return int + */ + public function version():int{ + return $this->version; + } + + /** + * @return int + */ + public function eccLevel():int{ + return $this->eclevel; + } + + /** + * @return int + */ + public function maskPattern():int{ + return $this->maskPattern; + } + + /** + * Returns the absoulute size of the matrix, including quiet zone (after setting it). + * + * size = version * 4 + 17 [ + 2 * quietzone size] + * + * @return int + */ + public function size():int{ + return $this->moduleCount; + } + + /** + * Returns the value of the module at position [$x, $y] + * + * @param int $x + * @param int $y + * + * @return int + */ + public function get(int $x, int $y):int{ + return $this->matrix[$y][$x]; + } + + /** + * Sets the $M_TYPE value for the module at position [$x, $y] + * + * true => $M_TYPE << 8 + * false => $M_TYPE + * + * @param int $x + * @param int $y + * @param int $M_TYPE + * @param bool $value + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function set(int $x, int $y, bool $value, int $M_TYPE):QRMatrix{ + $this->matrix[$y][$x] = $M_TYPE << ($value ? 8 : 0); + + return $this; + } + + /** + * Checks whether a module is true (dark) or false (light) + * + * true => $value >> 8 === $M_TYPE + * $value >> 8 > 0 + * + * false => $value === $M_TYPE + * $value >> 8 === 0 + * + * @param int $x + * @param int $y + * + * @return bool + */ + public function check(int $x, int $y):bool{ + return $this->matrix[$y][$x] >> 8 > 0; + } + + + /** + * Sets the "dark module", that is always on the same position 1x1px away from the bottom left finder + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function setDarkModule():QRMatrix{ + $this->set(8, 4 * $this->version + 9, true, $this::M_DARKMODULE); + + return $this; + } + + /** + * Draws the 7x7 finder patterns in the corners top left/right and bottom left + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function setFinderPattern():QRMatrix{ + + $pos = [ + [0, 0], // top left + [$this->moduleCount - 7, 0], // bottom left + [0, $this->moduleCount - 7], // top right + ]; + + foreach($pos as $c){ + for($y = 0; $y < 7; $y++){ + for($x = 0; $x < 7; $x++){ + // outer (dark) 7*7 square + if($x === 0 || $x === 6 || $y === 0 || $y === 6){ + $this->set($c[0] + $y, $c[1] + $x, true, $this::M_FINDER); + } + // inner (light) 5*5 square + elseif($x === 1 || $x === 5 || $y === 1 || $y === 5){ + $this->set($c[0] + $y, $c[1] + $x, false, $this::M_FINDER); + } + // 3*3 dot + else{ + $this->set($c[0] + $y, $c[1] + $x, true, $this::M_FINDER_DOT); + } + } + } + } + + return $this; + } + + /** + * Draws the separator lines around the finder patterns + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function setSeparators():QRMatrix{ + + $h = [ + [7, 0], + [$this->moduleCount - 8, 0], + [7, $this->moduleCount - 8], + ]; + + $v = [ + [7, 7], + [$this->moduleCount - 1, 7], + [7, $this->moduleCount - 8], + ]; + + for($c = 0; $c < 3; $c++){ + for($i = 0; $i < 8; $i++){ + $this->set($h[$c][0] , $h[$c][1] + $i, false, $this::M_SEPARATOR); + $this->set($v[$c][0] - $i, $v[$c][1] , false, $this::M_SEPARATOR); + } + } + + return $this; + } + + + /** + * Draws the 5x5 alignment patterns + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function setAlignmentPattern():QRMatrix{ + + foreach($this::alignmentPattern[$this->version] as $y){ + foreach($this::alignmentPattern[$this->version] as $x){ + + // skip existing patterns + if($this->matrix[$y][$x] !== $this::M_NULL){ + continue; + } + + for($ry = -2; $ry <= 2; $ry++){ + for($rx = -2; $rx <= 2; $rx++){ + $v = ($ry === 0 && $rx === 0) || $ry === 2 || $ry === -2 || $rx === 2 || $rx === -2; + + $this->set($x + $rx, $y + $ry, $v, $this::M_ALIGNMENT); + } + } + + } + } + + return $this; + } + + + /** + * Draws the timing pattern (h/v checkered line between the finder patterns) + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function setTimingPattern():QRMatrix{ + + foreach(range(8, $this->moduleCount - 8 - 1) as $i){ + + if($this->matrix[6][$i] !== $this::M_NULL || $this->matrix[$i][6] !== $this::M_NULL){ + continue; + } + + $v = $i % 2 === 0; + + $this->set($i, 6, $v, $this::M_TIMING); // h + $this->set(6, $i, $v, $this::M_TIMING); // v + } + + return $this; + } + + /** + * Draws the version information, 2x 3x6 pixel + * + * @param bool|null $test + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function setVersionNumber(bool $test = null):QRMatrix{ + $bits = $this::versionPattern[$this->version] ?? false; + + if($bits !== false){ + + for($i = 0; $i < 18; $i++){ + $a = (int)floor($i / 3); + $b = $i % 3 + $this->moduleCount - 8 - 3; + $v = !$test && (($bits >> $i) & 1) === 1; + + $this->set($b, $a, $v, $this::M_VERSION); // ne + $this->set($a, $b, $v, $this::M_VERSION); // sw + } + + } + + return $this; + } + + /** + * Draws the format info along the finder patterns + * + * @param int $maskPattern + * @param bool|null $test + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function setFormatInfo(int $maskPattern, bool $test = null):QRMatrix{ + $bits = $this::formatPattern[QRCode::ECC_MODES[$this->eclevel]][$maskPattern] ?? 0; + + for($i = 0; $i < 15; $i++){ + $v = !$test && (($bits >> $i) & 1) === 1; + + if($i < 6){ + $this->set(8, $i, $v, $this::M_FORMAT); + } + elseif($i < 8){ + $this->set(8, $i + 1, $v, $this::M_FORMAT); + } + else{ + $this->set(8, $this->moduleCount - 15 + $i, $v, $this::M_FORMAT); + } + + if($i < 8){ + $this->set($this->moduleCount - $i - 1, 8, $v, $this::M_FORMAT); + } + elseif($i < 9){ + $this->set(15 - $i, 8, $v, $this::M_FORMAT); + } + else{ + $this->set(15 - $i - 1, 8, $v, $this::M_FORMAT); + } + + } + + $this->set(8, $this->moduleCount - 8, !$test, $this::M_FORMAT); + + return $this; + } + + /** + * Draws the "quiet zone" of $size around the matrix + * + * @param int|null $size + * + * @return \chillerlan\QRCode\Data\QRMatrix + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function setQuietZone(int $size = null):QRMatrix{ + + if($this->matrix[$this->moduleCount - 1][$this->moduleCount - 1] === $this::M_NULL){ + throw new QRCodeDataException('use only after writing data'); + } + + $size = $size !== null + ? max(0, min($size, floor($this->moduleCount / 2))) + : 4; + + for($y = 0; $y < $this->moduleCount; $y++){ + for($i = 0; $i < $size; $i++){ + array_unshift($this->matrix[$y], $this::M_QUIETZONE); + array_push($this->matrix[$y], $this::M_QUIETZONE); + } + } + + $this->moduleCount += ($size * 2); + + $r = array_fill(0, $this->moduleCount, $this::M_QUIETZONE); + + for($i = 0; $i < $size; $i++){ + array_unshift($this->matrix, $r); + array_push($this->matrix, $r); + } + + return $this; + } + + /** + * Clears a space of $width * $height in order to add a logo or text. + * + * Additionally, the logo space can be positioned within the QR Code - respecting the main functional patterns - + * using $startX and $startY. If either of these are null, the logo space will be centered in that direction. + * ECC level "H" (30%) is required. + * + * Please note that adding a logo space minimizes the error correction capacity of the QR Code and + * created images may become unreadable, especially when printed with a chance to receive damage. + * Please test thoroughly before using this feature in production. + * + * This method should be called from within an output module (after the matrix has been filled with data). + * Note that there is no restiction on how many times this method could be called on the same matrix instance. + * + * @link https://github.com/chillerlan/php-qrcode/issues/52 + * + * @param int $width + * @param int $height + * @param int|null $startX + * @param int|null $startY + * + * @return \chillerlan\QRCode\Data\QRMatrix + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function setLogoSpace(int $width, int $height, int $startX = null, int $startY = null):QRMatrix{ + + // for logos we operate in ECC H (30%) only + if($this->eclevel !== 0b10){ + throw new QRCodeDataException('ECC level "H" required to add logo space'); + } + + // we need uneven sizes, adjust if needed + if(($width % 2) === 0){ + $width++; + } + + if(($height % 2) === 0){ + $height++; + } + + // $this->moduleCount includes the quiet zone (if created), we need the QR size here + $length = $this->version * 4 + 17; + + // throw if the logo space exceeds the maximum error correction capacity + if($width * $height > floor($length * $length * 0.2)){ + throw new QRCodeDataException('logo space exceeds the maximum error correction capacity'); + } + + // quiet zone size + $qz = ($this->moduleCount - $length) / 2; + // skip quiet zone and the first 9 rows/columns (finder-, mode-, version- and timing patterns) + $start = $qz + 9; + // skip quiet zone + $end = $this->moduleCount - $qz; + + // determine start coordinates + $startX = ($startX !== null ? $startX : ($length - $width) / 2) + $qz; + $startY = ($startY !== null ? $startY : ($length - $height) / 2) + $qz; + + // clear the space + foreach($this->matrix as $y => $row){ + foreach($row as $x => $val){ + // out of bounds, skip + if($x < $start || $y < $start ||$x >= $end || $y >= $end){ + continue; + } + // a match + if($x >= $startX && $x < ($startX + $width) && $y >= $startY && $y < ($startY + $height)){ + $this->set($x, $y, false, $this::M_LOGO); + } + } + } + + return $this; + } + + /** + * Maps the binary $data array from QRDataInterface::maskECC() on the matrix, using $maskPattern + * + * @see \chillerlan\QRCode\Data\QRDataAbstract::maskECC() + * + * @param int[] $data + * @param int $maskPattern + * + * @return \chillerlan\QRCode\Data\QRMatrix + */ + public function mapData(array $data, int $maskPattern):QRMatrix{ + $this->maskPattern = $maskPattern; + $byteCount = count($data); + $size = $this->moduleCount - 1; + $mask = $this->getMask($this->maskPattern); + + for($i = $size, $y = $size, $inc = -1, $byteIndex = 0, $bitIndex = 7; $i > 0; $i -= 2){ + + if($i === 6){ + $i--; + } + + while(true){ + for($c = 0; $c < 2; $c++){ + $x = $i - $c; + + if($this->matrix[$y][$x] === $this::M_NULL){ + $v = false; + + if($byteIndex < $byteCount){ + $v = (($data[$byteIndex] >> $bitIndex) & 1) === 1; + } + + if($mask($x, $y) === 0){ + $v = !$v; + } + + $this->matrix[$y][$x] = $this::M_DATA << ($v ? 8 : 0); + $bitIndex--; + + if($bitIndex === -1){ + $byteIndex++; + $bitIndex = 7; + } + + } + } + + $y += $inc; + + if($y < 0 || $this->moduleCount <= $y){ + $y -= $inc; + $inc = -$inc; + + break; + } + + } + } + + return $this; + } + + /** + * ISO/IEC 18004:2000 Section 8.8.1 + * + * Note that some versions of the QR code standard have had errors in the section about mask patterns. + * The information below has been corrected. (https://www.thonky.com/qr-code-tutorial/mask-patterns) + * + * @see \chillerlan\QRCode\QRMatrix::mapData() + * + * @internal + * + * @param int $maskPattern + * + * @return \Closure + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + protected function getMask(int $maskPattern):Closure{ + + if((0b111 & $maskPattern) !== $maskPattern){ + throw new QRCodeDataException('invalid mask pattern'); // @codeCoverageIgnore + } + + return [ + 0b000 => function($x, $y):int{ return ($x + $y) % 2; }, + 0b001 => function($x, $y):int{ return $y % 2; }, + 0b010 => function($x, $y):int{ return $x % 3; }, + 0b011 => function($x, $y):int{ return ($x + $y) % 3; }, + 0b100 => function($x, $y):int{ return ((int)($y / 2) + (int)($x / 3)) % 2; }, + 0b101 => function($x, $y):int{ return (($x * $y) % 2) + (($x * $y) % 3); }, + 0b110 => function($x, $y):int{ return ((($x * $y) % 2) + (($x * $y) % 3)) % 2; }, + 0b111 => function($x, $y):int{ return ((($x * $y) % 3) + (($x + $y) % 2)) % 2; }, + ][$maskPattern]; + } + +} -- cgit v1.2.3