diff options
Diffstat (limited to 'vendor/chillerlan/php-qrcode/src/Data')
-rw-r--r-- | vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php | 116 | ||||
-rw-r--r-- | vendor/chillerlan/php-qrcode/src/Data/Byte.php | 67 | ||||
-rw-r--r-- | vendor/chillerlan/php-qrcode/src/Data/ECI.php | 165 | ||||
-rw-r--r-- | vendor/chillerlan/php-qrcode/src/Data/Hanzi.php | 206 | ||||
-rw-r--r-- | vendor/chillerlan/php-qrcode/src/Data/Kanji.php | 171 | ||||
-rw-r--r-- | vendor/chillerlan/php-qrcode/src/Data/MaskPatternTester.php | 203 | ||||
-rw-r--r-- | vendor/chillerlan/php-qrcode/src/Data/Number.php | 135 | ||||
-rw-r--r-- | vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php | 9 | ||||
-rw-r--r-- | vendor/chillerlan/php-qrcode/src/Data/QRData.php | 263 | ||||
-rw-r--r-- | vendor/chillerlan/php-qrcode/src/Data/QRDataAbstract.php | 311 | ||||
-rw-r--r-- | vendor/chillerlan/php-qrcode/src/Data/QRDataInterface.php | 200 | ||||
-rw-r--r-- | vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php | 61 | ||||
-rw-r--r-- | vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php | 63 | ||||
-rwxr-xr-x | vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php | 811 | ||||
-rw-r--r-- | vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php | 127 |
15 files changed, 1739 insertions, 1169 deletions
diff --git a/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php b/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php index 28d9d7563..93ef9fc59 100644 --- a/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php +++ b/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php @@ -2,9 +2,7 @@ /** * Class AlphaNum * - * @filesource AlphaNum.php * @created 25.11.2015 - * @package chillerlan\QRCode\Data * @author Smiley <smiley@chillerlan.net> * @copyright 2015 Smiley * @license MIT @@ -12,9 +10,8 @@ namespace chillerlan\QRCode\Data; -use chillerlan\QRCode\QRCode; - -use function ord, sprintf; +use chillerlan\QRCode\Common\{BitBuffer, Mode}; +use function ceil, intdiv, preg_match, strpos; /** * Alphanumeric mode: 0 to 9, A to Z, space, $ % * + - . / : @@ -22,39 +19,118 @@ use function ord, sprintf; * ISO/IEC 18004:2000 Section 8.3.3 * ISO/IEC 18004:2000 Section 8.4.3 */ -final class AlphaNum extends QRDataAbstract{ +final class AlphaNum extends QRDataModeAbstract{ - protected int $datamode = QRCode::DATA_ALPHANUM; + /** + * ISO/IEC 18004:2000 Table 5 + * + * @var string + */ + private const CHAR_MAP = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'; - protected array $lengthBits = [9, 11, 13]; + /** + * @inheritDoc + */ + public const DATAMODE = Mode::ALPHANUM; /** - * @inheritdoc + * @inheritDoc */ - protected function write(string $data):void{ + public function getLengthInBits():int{ + return (int)ceil($this->getCharCount() * (11 / 2)); + } - for($i = 0; $i + 1 < $this->strlen; $i += 2){ - $this->bitBuffer->put($this->getCharCode($data[$i]) * 45 + $this->getCharCode($data[$i + 1]), 11); + /** + * @inheritDoc + */ + public static function validateString(string $string):bool{ + return (bool)preg_match('/^[A-Z\d %$*+-.:\/]+$/', $string); + } + + /** + * @inheritDoc + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + $len = $this->getCharCount(); + + $bitBuffer + ->put(self::DATAMODE, 4) + ->put($len, $this::getLengthBits($versionNumber)) + ; + + // encode 2 characters in 11 bits + for($i = 0; ($i + 1) < $len; $i += 2){ + $bitBuffer->put( + ($this->ord($this->data[$i]) * 45 + $this->ord($this->data[($i + 1)])), + 11, + ); } - if($i < $this->strlen){ - $this->bitBuffer->put($this->getCharCode($data[$i]), 6); + // encode a remaining character in 6 bits + if($i < $len){ + $bitBuffer->put($this->ord($this->data[$i]), 6); } + return $this; } /** - * get the code for the given character + * @inheritDoc * - * @throws \chillerlan\QRCode\Data\QRCodeDataException on an illegal character occurence + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + $result = ''; + // Read two characters at a time + while($length > 1){ + + if($bitBuffer->available() < 11){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $nextTwoCharsBits = $bitBuffer->read(11); + $result .= self::chr(intdiv($nextTwoCharsBits, 45)); + $result .= self::chr($nextTwoCharsBits % 45); + $length -= 2; + } + + if($length === 1){ + // special case: one character left + if($bitBuffer->available() < 6){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $result .= self::chr($bitBuffer->read(6)); + } + + return $result; + } + + /** + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + private function ord(string $chr):int{ + /** @phan-suppress-next-line PhanParamSuspiciousOrder */ + $ord = strpos(self::CHAR_MAP, $chr); + + if($ord === false){ + throw new QRCodeDataException('invalid character'); // @codeCoverageIgnore + } + + return $ord; + } + + /** + * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - protected function getCharCode(string $chr):int{ + private static function chr(int $ord):string{ - if(!isset($this::CHAR_MAP_ALPHANUM[$chr])){ - throw new QRCodeDataException(sprintf('illegal char: "%s" [%d]', $chr, ord($chr))); + if($ord < 0 || $ord > 44){ + throw new QRCodeDataException('invalid character code'); // @codeCoverageIgnore } - return $this::CHAR_MAP_ALPHANUM[$chr]; + return self::CHAR_MAP[$ord]; } } diff --git a/vendor/chillerlan/php-qrcode/src/Data/Byte.php b/vendor/chillerlan/php-qrcode/src/Data/Byte.php index 02e76a639..10ab85262 100644 --- a/vendor/chillerlan/php-qrcode/src/Data/Byte.php +++ b/vendor/chillerlan/php-qrcode/src/Data/Byte.php @@ -2,9 +2,7 @@ /** * Class Byte * - * @filesource Byte.php * @created 25.11.2015 - * @package chillerlan\QRCode\Data * @author Smiley <smiley@chillerlan.net> * @copyright 2015 Smiley * @license MIT @@ -12,33 +10,76 @@ namespace chillerlan\QRCode\Data; -use chillerlan\QRCode\QRCode; - -use function ord; +use chillerlan\QRCode\Common\{BitBuffer, Mode}; +use function chr, ord; /** - * Byte mode, ISO-8859-1 or UTF-8 + * 8-bit Byte mode, ISO-8859-1 or UTF-8 * * ISO/IEC 18004:2000 Section 8.3.4 * ISO/IEC 18004:2000 Section 8.4.4 */ -final class Byte extends QRDataAbstract{ +final class Byte extends QRDataModeAbstract{ - protected int $datamode = QRCode::DATA_BYTE; + /** + * @inheritDoc + */ + public const DATAMODE = Mode::BYTE; - protected array $lengthBits = [8, 16, 16]; + /** + * @inheritDoc + */ + public function getLengthInBits():int{ + return ($this->getCharCount() * 8); + } /** - * @inheritdoc + * @inheritDoc */ - protected function write(string $data):void{ + public static function validateString(string $string):bool{ + return $string !== ''; + } + + /** + * @inheritDoc + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + $len = $this->getCharCount(); + + $bitBuffer + ->put(self::DATAMODE, 4) + ->put($len, $this::getLengthBits($versionNumber)) + ; + $i = 0; - while($i < $this->strlen){ - $this->bitBuffer->put(ord($data[$i]), 8); + while($i < $len){ + $bitBuffer->put(ord($this->data[$i]), 8); $i++; } + return $this; + } + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + + if($bitBuffer->available() < (8 * $length)){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $readBytes = ''; + + for($i = 0; $i < $length; $i++){ + $readBytes .= chr($bitBuffer->read(8)); + } + + return $readBytes; } } diff --git a/vendor/chillerlan/php-qrcode/src/Data/ECI.php b/vendor/chillerlan/php-qrcode/src/Data/ECI.php new file mode 100644 index 000000000..1aacd0b71 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Data/ECI.php @@ -0,0 +1,165 @@ +<?php +/** + * Class ECI + * + * @created 20.11.2020 + * @author smiley <smiley@chillerlan.net> + * @copyright 2020 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, ECICharset, Mode}; +use function mb_convert_encoding, mb_detect_encoding, mb_internal_encoding, sprintf; + +/** + * Adds an ECI Designator + * + * ISO/IEC 18004:2000 8.4.1.1 + * + * Please note that you have to take care for the correct data encoding when adding with QRCode::add*Segment() + */ +final class ECI extends QRDataModeAbstract{ + + /** + * @inheritDoc + */ + public const DATAMODE = Mode::ECI; + + /** + * The current ECI encoding id + */ + private int $encoding; + + /** + * @inheritDoc + * @throws \chillerlan\QRCode\Data\QRCodeDataException + * @noinspection PhpMissingParentConstructorInspection + */ + public function __construct(int $encoding){ + + if($encoding < 0 || $encoding > 999999){ + throw new QRCodeDataException(sprintf('invalid encoding id: "%s"', $encoding)); + } + + $this->encoding = $encoding; + } + + /** + * @inheritDoc + */ + public function getLengthInBits():int{ + + if($this->encoding < 128){ + return 8; + } + + if($this->encoding < 16384){ + return 16; + } + + return 24; + } + + /** + * Writes an ECI designator to the bitbuffer + * + * @inheritDoc + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + $bitBuffer->put(self::DATAMODE, 4); + + if($this->encoding < 128){ + $bitBuffer->put($this->encoding, 8); + } + elseif($this->encoding < 16384){ + $bitBuffer->put(($this->encoding | 0x8000), 16); + } + elseif($this->encoding < 1000000){ + $bitBuffer->put(($this->encoding | 0xC00000), 24); + } + else{ + throw new QRCodeDataException('invalid ECI ID'); + } + + return $this; + } + + /** + * Reads and parses the value of an ECI designator + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function parseValue(BitBuffer $bitBuffer):ECICharset{ + $firstByte = $bitBuffer->read(8); + + // just one byte + if(($firstByte & 0b10000000) === 0){ + $id = ($firstByte & 0b01111111); + } + // two bytes + elseif(($firstByte & 0b11000000) === 0b10000000){ + $id = ((($firstByte & 0b00111111) << 8) | $bitBuffer->read(8)); + } + // three bytes + elseif(($firstByte & 0b11100000) === 0b11000000){ + $id = ((($firstByte & 0b00011111) << 16) | $bitBuffer->read(16)); + } + else{ + throw new QRCodeDataException(sprintf('error decoding ECI value first byte: %08b', $firstByte));// @codeCoverageIgnore + } + + return new ECICharset($id); + } + + /** + * @codeCoverageIgnore Unused, but required as per interface + */ + public static function validateString(string $string):bool{ + return true; + } + + /** + * Reads and decodes the ECI designator including the following byte sequence + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $eciCharset = self::parseValue($bitBuffer); + $nextMode = $bitBuffer->read(4); + $data = self::decodeModeSegment($nextMode, $bitBuffer, $versionNumber); + $encoding = $eciCharset->getName(); + + if($encoding === null){ + // The spec isn't clear on this mode; see + // section 6.4.5: it does not say which encoding to assuming + // upon decoding. I have seen ISO-8859-1 used as well as + // Shift_JIS -- without anything like an ECI designator to + // give a hint. + $encoding = mb_detect_encoding($data, ['ISO-8859-1', 'Windows-1252', 'SJIS', 'UTF-8'], true); + + if($encoding === false){ + throw new QRCodeDataException('could not determine encoding in ECI mode'); // @codeCoverageIgnore + } + } + + return mb_convert_encoding($data, mb_internal_encoding(), $encoding); + } + + /** + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + private static function decodeModeSegment(int $mode, BitBuffer $bitBuffer, int $versionNumber):string{ + + switch(true){ + case $mode === Mode::NUMBER: return Number::decodeSegment($bitBuffer, $versionNumber); + case $mode === Mode::ALPHANUM: return AlphaNum::decodeSegment($bitBuffer, $versionNumber); + case $mode === Mode::BYTE: return Byte::decodeSegment($bitBuffer, $versionNumber); + } + + throw new QRCodeDataException(sprintf('ECI designator followed by invalid mode: "%04b"', $mode)); + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Data/Hanzi.php b/vendor/chillerlan/php-qrcode/src/Data/Hanzi.php new file mode 100644 index 000000000..2df8ac1c6 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Data/Hanzi.php @@ -0,0 +1,206 @@ +<?php +/** + * Class Hanzi + * + * @created 19.11.2020 + * @author smiley <smiley@chillerlan.net> + * @copyright 2020 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, Mode}; +use Throwable; +use function chr, implode, intdiv, is_string, mb_convert_encoding, mb_detect_encoding, + mb_detect_order, mb_internal_encoding, mb_strlen, ord, sprintf, strlen; + +/** + * Hanzi (simplified Chinese) mode, GBT18284-2000: 13-bit double-byte characters from the GB2312/GB18030 character set + * + * Please note that this is not part of the QR Code specification and may not be supported by all readers (ZXing-based ones do). + * + * @see https://en.wikipedia.org/wiki/GB_2312 + * @see http://www.herongyang.com/GB2312/Introduction-of-GB2312.html + * @see https://en.wikipedia.org/wiki/GBK_(character_encoding)#Encoding + * @see https://gist.github.com/codemasher/91da33c44bfb48a81a6c1426bb8e4338 + * @see https://github.com/zxing/zxing/blob/dfb06fa33b17a9e68321be151c22846c7b78048f/core/src/main/java/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java#L172-L209 + * @see https://www.chinesestandard.net/PDF/English.aspx/GBT18284-2000 + */ +final class Hanzi extends QRDataModeAbstract{ + + /** + * possible values: GB2312, GB18030 + * + * @var string + */ + public const ENCODING = 'GB18030'; + + /** + * @todo: other subsets??? + * + * @var int + */ + public const GB2312_SUBSET = 0b0001; + + /** + * @inheritDoc + */ + public const DATAMODE = Mode::HANZI; + + /** + * @inheritDoc + */ + protected function getCharCount():int{ + return mb_strlen($this->data, self::ENCODING); + } + + /** + * @inheritDoc + */ + public function getLengthInBits():int{ + return ($this->getCharCount() * 13); + } + + /** + * @inheritDoc + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function convertEncoding(string $string):string{ + mb_detect_order([mb_internal_encoding(), 'UTF-8', 'GB2312', 'GB18030', 'CP936', 'EUC-CN', 'HZ']); + + $detected = mb_detect_encoding($string, null, true); + + if($detected === false){ + throw new QRCodeDataException('mb_detect_encoding error'); + } + + if($detected === self::ENCODING){ + return $string; + } + + $string = mb_convert_encoding($string, self::ENCODING, $detected); + + if(!is_string($string)){ + throw new QRCodeDataException('mb_convert_encoding error'); + } + + return $string; + } + + /** + * checks if a string qualifies as Hanzi/GB2312 + */ + public static function validateString(string $string):bool{ + + try{ + $string = self::convertEncoding($string); + } + catch(Throwable $e){ + return false; + } + + $len = strlen($string); + + if($len < 2 || ($len % 2) !== 0){ + return false; + } + + for($i = 0; $i < $len; $i += 2){ + $byte1 = ord($string[$i]); + $byte2 = ord($string[($i + 1)]); + + // byte 1 unused ranges + if($byte1 < 0xa1 || ($byte1 > 0xa9 && $byte1 < 0xb0) || $byte1 > 0xf7){ + return false; + } + + // byte 2 unused ranges + if($byte2 < 0xa1 || $byte2 > 0xfe){ + return false; + } + + } + + return true; + } + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException on an illegal character occurence + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + + $bitBuffer + ->put(self::DATAMODE, 4) + ->put($this::GB2312_SUBSET, 4) + ->put($this->getCharCount(), $this::getLengthBits($versionNumber)) + ; + + $len = strlen($this->data); + + for($i = 0; ($i + 1) < $len; $i += 2){ + $c = (((0xff & ord($this->data[$i])) << 8) | (0xff & ord($this->data[($i + 1)]))); + + if($c >= 0xa1a1 && $c <= 0xaafe){ + $c -= 0x0a1a1; + } + elseif($c >= 0xb0a1 && $c <= 0xfafe){ + $c -= 0x0a6a1; + } + else{ + throw new QRCodeDataException(sprintf('illegal char at %d [%d]', ($i + 1), $c)); + } + + $bitBuffer->put((((($c >> 8) & 0xff) * 0x060) + ($c & 0xff)), 13); + } + + if($i < $len){ + throw new QRCodeDataException(sprintf('illegal char at %d', ($i + 1))); + } + + return $this; + } + + /** + * See specification GBT 18284-2000 + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + + // Hanzi mode contains a subset indicator right after mode indicator + if($bitBuffer->read(4) !== self::GB2312_SUBSET){ + throw new QRCodeDataException('ecpected subset indicator for Hanzi mode'); + } + + $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + + if($bitBuffer->available() < ($length * 13)){ + throw new QRCodeDataException('not enough bits available'); + } + + // Each character will require 2 bytes. Read the characters as 2-byte pairs and decode as GB2312 afterwards + $buffer = []; + $offset = 0; + + while($length > 0){ + // Each 13 bits encodes a 2-byte character + $twoBytes = $bitBuffer->read(13); + $assembledTwoBytes = ((intdiv($twoBytes, 0x060) << 8) | ($twoBytes % 0x060)); + + $assembledTwoBytes += ($assembledTwoBytes < 0x00a00) // 0x003BF + ? 0x0a1a1 // In the 0xA1A1 to 0xAAFE range + : 0x0a6a1; // In the 0xB0A1 to 0xFAFE range + + $buffer[$offset] = chr(0xff & ($assembledTwoBytes >> 8)); + $buffer[($offset + 1)] = chr(0xff & $assembledTwoBytes); + $offset += 2; + $length--; + } + + return mb_convert_encoding(implode('', $buffer), mb_internal_encoding(), self::ENCODING); + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Data/Kanji.php b/vendor/chillerlan/php-qrcode/src/Data/Kanji.php index e106c50f1..ccde82642 100644 --- a/vendor/chillerlan/php-qrcode/src/Data/Kanji.php +++ b/vendor/chillerlan/php-qrcode/src/Data/Kanji.php @@ -2,9 +2,7 @@ /** * Class Kanji * - * @filesource Kanji.php * @created 25.11.2015 - * @package chillerlan\QRCode\Data * @author Smiley <smiley@chillerlan.net> * @copyright 2015 Smiley * @license MIT @@ -12,58 +10,183 @@ namespace chillerlan\QRCode\Data; -use chillerlan\QRCode\QRCode; - -use function mb_strlen, ord, sprintf, strlen; +use chillerlan\QRCode\Common\{BitBuffer, Mode}; +use Throwable; +use function chr, implode, intdiv, is_string, mb_convert_encoding, mb_detect_encoding, + mb_detect_order, mb_internal_encoding, mb_strlen, ord, sprintf, strlen; /** - * Kanji mode: double-byte characters from the Shift JIS character set + * Kanji mode: 13-bit double-byte characters from the Shift-JIS character set * * ISO/IEC 18004:2000 Section 8.3.5 * ISO/IEC 18004:2000 Section 8.4.5 + * + * @see https://en.wikipedia.org/wiki/Shift_JIS#As_defined_in_JIS_X_0208:1997 + * @see http://www.rikai.com/library/kanjitables/kanji_codes.sjis.shtml + * @see https://gist.github.com/codemasher/d07d3e6e9346c08e7a41b8b978784952 */ -final class Kanji extends QRDataAbstract{ +final class Kanji extends QRDataModeAbstract{ - protected int $datamode = QRCode::DATA_KANJI; + /** + * possible values: SJIS, SJIS-2004 + * + * SJIS-2004 may produce errors in PHP < 8 + * + * @var string + */ + public const ENCODING = 'SJIS'; - protected array $lengthBits = [8, 10, 12]; + /** + * @inheritDoc + */ + public const DATAMODE = Mode::KANJI; + + /** + * @inheritDoc + */ + protected function getCharCount():int{ + return mb_strlen($this->data, self::ENCODING); + } + + /** + * @inheritDoc + */ + public function getLengthInBits():int{ + return ($this->getCharCount() * 13); + } /** - * @inheritdoc + * @inheritDoc + * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - protected function getLength(string $data):int{ - return mb_strlen($data, 'SJIS'); + public static function convertEncoding(string $string):string{ + mb_detect_order([mb_internal_encoding(), 'UTF-8', 'SJIS', 'SJIS-2004']); + + $detected = mb_detect_encoding($string, null, true); + + if($detected === false){ + throw new QRCodeDataException('mb_detect_encoding error'); + } + + if($detected === self::ENCODING){ + return $string; + } + + $string = mb_convert_encoding($string, self::ENCODING, $detected); + + if(!is_string($string)){ + throw new QRCodeDataException(sprintf('invalid encoding: %s', $detected)); + } + + return $string; } /** - * @inheritdoc + * checks if a string qualifies as SJIS Kanji + */ + public static function validateString(string $string):bool{ + + try{ + $string = self::convertEncoding($string); + } + catch(Throwable $e){ + return false; + } + + $len = strlen($string); + + if($len < 2 || ($len % 2) !== 0){ + return false; + } + + for($i = 0; $i < $len; $i += 2){ + $byte1 = ord($string[$i]); + $byte2 = ord($string[($i + 1)]); + + // byte 1 unused and vendor ranges + if($byte1 < 0x81 || ($byte1 > 0x84 && $byte1 < 0x88) || ($byte1 > 0x9f && $byte1 < 0xe0) || $byte1 > 0xea){ + return false; + } + + // byte 2 unused ranges + if($byte2 < 0x40 || $byte2 === 0x7f || $byte2 > 0xfc){ + return false; + } + + } + + return true; + } + + /** + * @inheritDoc * * @throws \chillerlan\QRCode\Data\QRCodeDataException on an illegal character occurence */ - protected function write(string $data):void{ - $len = strlen($data); + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + + $bitBuffer + ->put(self::DATAMODE, 4) + ->put($this->getCharCount(), $this::getLengthBits($versionNumber)) + ; + + $len = strlen($this->data); - for($i = 0; $i + 1 < $len; $i += 2){ - $c = ((0xff & ord($data[$i])) << 8) | (0xff & ord($data[$i + 1])); + for($i = 0; ($i + 1) < $len; $i += 2){ + $c = (((0xff & ord($this->data[$i])) << 8) | (0xff & ord($this->data[($i + 1)]))); - if($c >= 0x8140 && $c <= 0x9FFC){ + if($c >= 0x8140 && $c <= 0x9ffc){ $c -= 0x8140; } - elseif($c >= 0xE040 && $c <= 0xEBBF){ - $c -= 0xC140; + elseif($c >= 0xe040 && $c <= 0xebbf){ + $c -= 0xc140; } else{ - throw new QRCodeDataException(sprintf('illegal char at %d [%d]', $i + 1, $c)); + throw new QRCodeDataException(sprintf('illegal char at %d [%d]', ($i + 1), $c)); } - $this->bitBuffer->put(((($c >> 8) & 0xff) * 0xC0) + ($c & 0xff), 13); - + $bitBuffer->put((((($c >> 8) & 0xff) * 0xc0) + ($c & 0xff)), 13); } if($i < $len){ - throw new QRCodeDataException(sprintf('illegal char at %d', $i + 1)); + throw new QRCodeDataException(sprintf('illegal char at %d', ($i + 1))); + } + + return $this; + } + + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + + if($bitBuffer->available() < ($length * 13)){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + // Each character will require 2 bytes. Read the characters as 2-byte pairs and decode as SJIS afterwards + $buffer = []; + $offset = 0; + + while($length > 0){ + // Each 13 bits encodes a 2-byte character + $twoBytes = $bitBuffer->read(13); + $assembledTwoBytes = ((intdiv($twoBytes, 0x0c0) << 8) | ($twoBytes % 0x0c0)); + + $assembledTwoBytes += ($assembledTwoBytes < 0x01f00) + ? 0x08140 // In the 0x8140 to 0x9FFC range + : 0x0c140; // In the 0xE040 to 0xEBBF range + + $buffer[$offset] = chr(0xff & ($assembledTwoBytes >> 8)); + $buffer[($offset + 1)] = chr(0xff & $assembledTwoBytes); + $offset += 2; + $length--; } + return mb_convert_encoding(implode('', $buffer), mb_internal_encoding(), self::ENCODING); } } diff --git a/vendor/chillerlan/php-qrcode/src/Data/MaskPatternTester.php b/vendor/chillerlan/php-qrcode/src/Data/MaskPatternTester.php deleted file mode 100644 index 7874cb53d..000000000 --- a/vendor/chillerlan/php-qrcode/src/Data/MaskPatternTester.php +++ /dev/null @@ -1,203 +0,0 @@ -<?php -/** - * Class MaskPatternTester - * - * @filesource MaskPatternTester.php - * @created 22.11.2017 - * @package chillerlan\QRCode\Data - * @author Smiley <smiley@chillerlan.net> - * @copyright 2017 Smiley - * @license MIT - * - * @noinspection PhpUnused - */ - -namespace chillerlan\QRCode\Data; - -use function abs, array_search, call_user_func_array, min; - -/** - * Receives a QRDataInterface object and runs the mask pattern tests on it. - * - * ISO/IEC 18004:2000 Section 8.8.2 - Evaluation of masking results - * - * @see http://www.thonky.com/qr-code-tutorial/data-masking - */ -final class MaskPatternTester{ - - /** - * The data interface that contains the data matrix to test - */ - protected QRDataInterface $dataInterface; - - /** - * Receives the QRDataInterface - * - * @see \chillerlan\QRCode\QROptions::$maskPattern - * @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern - */ - public function __construct(QRDataInterface $dataInterface){ - $this->dataInterface = $dataInterface; - } - - /** - * shoves a QRMatrix through the MaskPatternTester to find the lowest penalty mask pattern - * - * @see \chillerlan\QRCode\Data\MaskPatternTester - */ - public function getBestMaskPattern():int{ - $penalties = []; - - for($pattern = 0; $pattern < 8; $pattern++){ - $penalties[$pattern] = $this->testPattern($pattern); - } - - return array_search(min($penalties), $penalties, true); - } - - /** - * Returns the penalty for the given mask pattern - * - * @see \chillerlan\QRCode\QROptions::$maskPattern - * @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern - */ - public function testPattern(int $pattern):int{ - $matrix = $this->dataInterface->initMatrix($pattern, true); - $penalty = 0; - - for($level = 1; $level <= 4; $level++){ - $penalty += call_user_func_array([$this, 'testLevel'.$level], [$matrix->matrix(true), $matrix->size()]); - } - - return (int)$penalty; - } - - /** - * Checks for each group of five or more same-colored modules in a row (or column) - */ - protected function testLevel1(array $m, int $size):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 || $size <= $y + $ry){ - continue; - } - - for($rx = -1; $rx <= 1; $rx++){ - - if(($ry === 0 && $rx === 0) || (($x + $rx) < 0 || $size <= ($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 - */ - protected function testLevel2(array $m, int $size):int{ - $penalty = 0; - - foreach($m as $y => $row){ - - if($y > $size - 2){ - break; - } - - foreach($row as $x => $val){ - - if($x > $size - 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) - */ - protected function testLevel3(array $m, int $size):int{ - $penalties = 0; - - foreach($m as $y => $row){ - foreach($row as $x => $val){ - - if( - $x + 6 < $size - && $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 < $size - && $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 - */ - protected function testLevel4(array $m, int $size):float{ - $count = 0; - - foreach($m as $y => $row){ - foreach($row as $x => $val){ - if($val){ - $count++; - } - } - } - - return (abs(100 * $count / $size / $size - 50) / 5) * 10; - } - -} diff --git a/vendor/chillerlan/php-qrcode/src/Data/Number.php b/vendor/chillerlan/php-qrcode/src/Data/Number.php index 0a905b13e..3e4238fdc 100644 --- a/vendor/chillerlan/php-qrcode/src/Data/Number.php +++ b/vendor/chillerlan/php-qrcode/src/Data/Number.php @@ -2,9 +2,7 @@ /** * Class Number * - * @filesource Number.php * @created 26.11.2015 - * @package chillerlan\QRCode\Data * @author Smiley <smiley@chillerlan.net> * @copyright 2015 Smiley * @license MIT @@ -12,9 +10,8 @@ namespace chillerlan\QRCode\Data; -use chillerlan\QRCode\QRCode; - -use function ord, sprintf, str_split, substr; +use chillerlan\QRCode\Common\{BitBuffer, Mode}; +use function ceil, intdiv, substr, unpack; /** * Numeric mode: decimal digits 0 to 9 @@ -22,56 +19,142 @@ use function ord, sprintf, str_split, substr; * ISO/IEC 18004:2000 Section 8.3.2 * ISO/IEC 18004:2000 Section 8.4.2 */ -final class Number extends QRDataAbstract{ +final class Number extends QRDataModeAbstract{ - protected int $datamode = QRCode::DATA_NUMBER; + /** + * @inheritDoc + */ + public const DATAMODE = Mode::NUMBER; + + /** + * @inheritDoc + */ + public function getLengthInBits():int{ + return (int)ceil($this->getCharCount() * (10 / 3)); + } - protected array $lengthBits = [10, 12, 14]; + /** + * @inheritDoc + */ + public static function validateString(string $string):bool{ + return (bool)preg_match('/^\d+$/', $string); + } /** - * @inheritdoc + * @inheritDoc */ - protected function write(string $data):void{ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{ + $len = $this->getCharCount(); + + $bitBuffer + ->put(self::DATAMODE, 4) + ->put($len, $this::getLengthBits($versionNumber)) + ; + $i = 0; - while($i + 2 < $this->strlen){ - $this->bitBuffer->put($this->parseInt(substr($data, $i, 3)), 10); + // encode numeric triplets in 10 bits + while(($i + 2) < $len){ + $bitBuffer->put($this->parseInt(substr($this->data, $i, 3)), 10); $i += 3; } - if($i < $this->strlen){ + if($i < $len){ - if($this->strlen - $i === 1){ - $this->bitBuffer->put($this->parseInt(substr($data, $i, $i + 1)), 4); + // encode 2 remaining numbers in 7 bits + if(($len - $i) === 2){ + $bitBuffer->put($this->parseInt(substr($this->data, $i, 2)), 7); } - elseif($this->strlen - $i === 2){ - $this->bitBuffer->put($this->parseInt(substr($data, $i, $i + 2)), 7); + // encode one remaining number in 4 bits + elseif(($len - $i) === 1){ + $bitBuffer->put($this->parseInt(substr($this->data, $i, 1)), 4); } } + return $this; } /** * get the code for the given numeric string * - * @throws \chillerlan\QRCode\Data\QRCodeDataException on an illegal character occurence + * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - protected function parseInt(string $string):int{ + private function parseInt(string $string):int{ $num = 0; - foreach(str_split($string) as $chr){ - $c = ord($chr); + $ords = unpack('C*', $string); - if(!isset($this::CHAR_MAP_NUMBER[$chr])){ - throw new QRCodeDataException(sprintf('illegal char: "%s" [%d]', $chr, $c)); - } + if($ords === false){ + throw new QRCodeDataException('unpack() error'); + } - $c = $c - 48; // ord('0') - $num = $num * 10 + $c; + foreach($ords as $ord){ + $num = ($num * 10 + $ord - 48); } return $num; } + /** + * @inheritDoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + $result = ''; + // Read three digits at a time + while($length >= 3){ + // Each 10 bits encodes three digits + if($bitBuffer->available() < 10){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $threeDigitsBits = $bitBuffer->read(10); + + if($threeDigitsBits >= 1000){ + throw new QRCodeDataException('error decoding numeric value'); + } + + $result .= intdiv($threeDigitsBits, 100); + $result .= (intdiv($threeDigitsBits, 10) % 10); + $result .= ($threeDigitsBits % 10); + + $length -= 3; + } + + if($length === 2){ + // Two digits left over to read, encoded in 7 bits + if($bitBuffer->available() < 7){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $twoDigitsBits = $bitBuffer->read(7); + + if($twoDigitsBits >= 100){ + throw new QRCodeDataException('error decoding numeric value'); + } + + $result .= intdiv($twoDigitsBits, 10); + $result .= ($twoDigitsBits % 10); + } + elseif($length === 1){ + // One digit left over to read + if($bitBuffer->available() < 4){ + throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore + } + + $digitBits = $bitBuffer->read(4); + + if($digitBits >= 10){ + throw new QRCodeDataException('error decoding numeric value'); + } + + $result .= $digitBits; + } + + return $result; + } + } diff --git a/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php b/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php index 862f57ba0..04ffbd7ed 100644 --- a/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php +++ b/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php @@ -2,9 +2,7 @@ /** * Class QRCodeDataException * - * @filesource QRCodeDataException.php * @created 09.12.2015 - * @package chillerlan\QRCode\Data * @author Smiley <smiley@chillerlan.net> * @copyright 2015 Smiley * @license MIT @@ -14,4 +12,9 @@ namespace chillerlan\QRCode\Data; use chillerlan\QRCode\QRCodeException; -class QRCodeDataException extends QRCodeException{} +/** + * An exception container + */ +final class QRCodeDataException extends QRCodeException{ + +} diff --git a/vendor/chillerlan/php-qrcode/src/Data/QRData.php b/vendor/chillerlan/php-qrcode/src/Data/QRData.php new file mode 100644 index 000000000..9d610b357 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Data/QRData.php @@ -0,0 +1,263 @@ +<?php +/** + * Class QRData + * + * @created 25.11.2015 + * @author Smiley <smiley@chillerlan.net> + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, EccLevel, Mode, Version}; +use chillerlan\Settings\SettingsContainerInterface; +use function count, sprintf; + +/** + * Processes the binary data and maps it on a QRMatrix which is then being returned + */ +final class QRData{ + + /** + * the options instance + * + * @var \chillerlan\Settings\SettingsContainerInterface|\chillerlan\QRCode\QROptions + */ + private SettingsContainerInterface $options; + + /** + * a BitBuffer instance + */ + private BitBuffer $bitBuffer; + + /** + * an EccLevel instance + */ + private EccLevel $eccLevel; + + /** + * current QR Code version + */ + private Version $version; + + /** + * @var \chillerlan\QRCode\Data\QRDataModeInterface[] + */ + private array $dataSegments = []; + + /** + * Max bits for the current ECC mode + * + * @var int[] + */ + private array $maxBitsForEcc; + + /** + * QRData constructor. + */ + public function __construct(SettingsContainerInterface $options, array $dataSegments = []){ + $this->options = $options; + $this->bitBuffer = new BitBuffer; + $this->eccLevel = new EccLevel($this->options->eccLevel); + $this->maxBitsForEcc = $this->eccLevel->getMaxBits(); + + $this->setData($dataSegments); + } + + /** + * Sets the data string (internally called by the constructor) + * + * Subsequent calls will overwrite the current state - use the QRCode::add*Segement() method instead + * + * @param \chillerlan\QRCode\Data\QRDataModeInterface[] $dataSegments + */ + public function setData(array $dataSegments):self{ + $this->dataSegments = $dataSegments; + $this->version = $this->getMinimumVersion(); + + $this->bitBuffer->clear(); + $this->writeBitBuffer(); + + return $this; + } + + /** + * Returns the current BitBuffer instance + * + * @codeCoverageIgnore + */ + public function getBitBuffer():BitBuffer{ + return $this->bitBuffer; + } + + /** + * Sets a BitBuffer object + * + * This can be used instead of setData(), however, the version auto-detection is not available in this case. + * The version needs to match the length bits range for the data mode the data has been encoded with, + * additionally the bit array needs to contain enough pad bits. + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function setBitBuffer(BitBuffer $bitBuffer):self{ + + if($this->options->version === Version::AUTO){ + throw new QRCodeDataException('version auto detection is not available'); + } + + if($bitBuffer->getLength() === 0){ + throw new QRCodeDataException('the given BitBuffer is empty'); + } + + $this->dataSegments = []; + $this->bitBuffer = $bitBuffer; + $this->version = new Version($this->options->version); + + return $this; + } + + /** + * returns a fresh matrix object with the data written and masked with the given $maskPattern + */ + public function writeMatrix():QRMatrix{ + return (new QRMatrix($this->version, $this->eccLevel)) + ->initFunctionalPatterns() + ->writeCodewords($this->bitBuffer) + ; + } + + /** + * estimates the total length of the several mode segments in order to guess the minimum version + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function estimateTotalBitLength():int{ + $length = 0; + + foreach($this->dataSegments as $segment){ + // data length of the current segment + $length += $segment->getLengthInBits(); + // +4 bits for the mode descriptor + $length += 4; + // Hanzi mode sets an additional 4 bit long subset identifier + if($segment instanceof Hanzi){ + $length += 4; + } + } + + $provisionalVersion = null; + + foreach($this->maxBitsForEcc as $version => $maxBits){ + + if($length <= $maxBits){ + $provisionalVersion = $version; + } + + } + + if($provisionalVersion !== null){ + + // add character count indicator bits for the provisional version + foreach($this->dataSegments as $segment){ + $length += Mode::getLengthBitsForVersion($segment::DATAMODE, $provisionalVersion); + } + + // it seems that in some cases the estimated total length is not 100% accurate, + // so we substract 4 bits from the total when not in mixed mode + if(count($this->dataSegments) <= 1){ + $length -= 4; + } + + // we've got a match! + // or let's see if there's a higher version number available + if($length <= $this->maxBitsForEcc[$provisionalVersion] || isset($this->maxBitsForEcc[($provisionalVersion + 1)])){ + return $length; + } + + } + + throw new QRCodeDataException(sprintf('estimated data exceeds %d bits', $length)); + } + + /** + * returns the minimum version number for the given string + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function getMinimumVersion():Version{ + + if($this->options->version !== Version::AUTO){ + return new Version($this->options->version); + } + + $total = $this->estimateTotalBitLength(); + + // guess the version number within the given range + for($version = $this->options->versionMin; $version <= $this->options->versionMax; $version++){ + if($total <= ($this->maxBitsForEcc[$version] - 4)){ + return new Version($version); + } + } + + // it's almost impossible to run into this one as $this::estimateTotalBitLength() would throw first + throw new QRCodeDataException('failed to guess minimum version'); // @codeCoverageIgnore + } + + /** + * creates a BitBuffer and writes the string data to it + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException on data overflow + */ + private function writeBitBuffer():void{ + $MAX_BITS = $this->eccLevel->getMaxBitsForVersion($this->version); + + foreach($this->dataSegments as $segment){ + $segment->write($this->bitBuffer, $this->version->getVersionNumber()); + } + + // overflow, likely caused due to invalid version setting + if($this->bitBuffer->getLength() > $MAX_BITS){ + throw new QRCodeDataException( + sprintf('code length overflow. (%d > %d bit)', $this->bitBuffer->getLength(), $MAX_BITS) + ); + } + + // add terminator (ISO/IEC 18004:2000 Table 2) + if(($this->bitBuffer->getLength() + 4) <= $MAX_BITS){ + $this->bitBuffer->put(Mode::TERMINATOR, 4); + } + + // Padding: ISO/IEC 18004:2000 8.4.9 Bit stream to codeword conversion + + // if the final codeword is not exactly 8 bits in length, it shall be made 8 bits long + // by the addition of padding bits with binary value 0 + while(($this->bitBuffer->getLength() % 8) !== 0){ + + if($this->bitBuffer->getLength() === $MAX_BITS){ + break; + } + + $this->bitBuffer->putBit(false); + } + + // The message bit stream shall then be extended to fill the data capacity of the symbol + // corresponding to the Version and Error Correction Level, by the addition of the Pad + // Codewords 11101100 and 00010001 alternately. + $alternate = false; + + while(($this->bitBuffer->getLength() + 8) <= $MAX_BITS){ + $this->bitBuffer->put(($alternate) ? 0b00010001 : 0b11101100, 8); + + $alternate = !$alternate; + } + + // In certain versions of symbol, it may be necessary to add 3, 4 or 7 Remainder Bits (all zeros) + // to the end of the message in order exactly to fill the symbol capacity + while($this->bitBuffer->getLength() <= $MAX_BITS){ + $this->bitBuffer->putBit(false); + } + + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Data/QRDataAbstract.php b/vendor/chillerlan/php-qrcode/src/Data/QRDataAbstract.php deleted file mode 100644 index 72b67b7b9..000000000 --- a/vendor/chillerlan/php-qrcode/src/Data/QRDataAbstract.php +++ /dev/null @@ -1,311 +0,0 @@ -<?php -/** - * Class QRDataAbstract - * - * @filesource QRDataAbstract.php - * @created 25.11.2015 - * @package chillerlan\QRCode\Data - * @author Smiley <smiley@chillerlan.net> - * @copyright 2015 Smiley - * @license MIT - */ - -namespace chillerlan\QRCode\Data; - -use chillerlan\QRCode\QRCode; -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 - */ - protected ?int $strlen = null; - - /** - * the current data mode: Num, Alphanum, Kanji, Byte - */ - protected int $datamode; - - /** - * mode length bits for the version breakpoints 1-9, 10-26 and 27-40 - * - * ISO/IEC 18004:2000 Table 3 - Number of bits in Character Count Indicator - */ - protected array $lengthBits = [0, 0, 0]; - - /** - * current QR Code version - */ - protected int $version; - - /** - * ECC temp data - */ - protected array $ecdata; - - /** - * ECC temp data - */ - protected array $dcdata; - - /** - * the options instance - * - * @var \chillerlan\Settings\SettingsContainerInterface|\chillerlan\QRCode\QROptions - */ - protected SettingsContainerInterface $options; - - /** - * a BitBuffer instance - */ - protected BitBuffer $bitBuffer; - - /** - * QRDataInterface constructor. - */ - 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->writeBitBuffer($data); - - return $this; - } - - /** - * @inheritDoc - */ - public function initMatrix(int $maskPattern, bool $test = null):QRMatrix{ - return (new QRMatrix($this->version, $this->options->eccLevel)) - ->init($maskPattern, $test) - ->mapData($this->maskECC(), $maskPattern) - ; - } - - /** - * returns the length bits for the version breakpoints 1-9, 10-26 and 27-40 - * - * @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 - */ - protected function getLength(string $data):int{ - return strlen($data); - } - - /** - * returns the minimum version number for the given string - * - * @throws \chillerlan\QRCode\Data\QRCodeDataException - */ - protected function getMinimumVersion():int{ - $maxlength = 0; - - // guess the version number within the given range - $dataMode = QRCode::DATA_MODES[$this->datamode]; - $eccMode = QRCode::ECC_MODES[$this->options->eccLevel]; - - foreach(range($this->options->versionMin, $this->options->versionMax) as $version){ - $maxlength = $this::MAX_LENGTH[$version][$dataMode][$eccMode]; - - 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() - */ - abstract protected function write(string $data):void; - - /** - * creates a BitBuffer and writes the string data to it - * - * @throws \chillerlan\QRCode\QRCodeException on data overflow - */ - protected function writeBitBuffer(string $data):void{ - $this->bitBuffer = new BitBuffer; - - $MAX_BITS = $this::MAX_BITS[$this->version][QRCode::ECC_MODES[$this->options->eccLevel]]; - - $this->bitBuffer - ->put($this->datamode, 4) - ->put($this->strlen, $this->getLengthBits()) - ; - - $this->write($data); - - // overflow, likely caused due to invalid version setting - if($this->bitBuffer->getLength() > $MAX_BITS){ - throw new QRCodeDataException(sprintf('code length overflow. (%d > %d bit)', $this->bitBuffer->getLength(), $MAX_BITS)); - } - - // add terminator (ISO/IEC 18004:2000 Table 2) - if($this->bitBuffer->getLength() + 4 <= $MAX_BITS){ - $this->bitBuffer->put(0, 4); - } - - // padding - while($this->bitBuffer->getLength() % 8 !== 0){ - $this->bitBuffer->putBit(false); - } - - // padding - while(true){ - - if($this->bitBuffer->getLength() >= $MAX_BITS){ - break; - } - - $this->bitBuffer->put(0xEC, 8); - - if($this->bitBuffer->getLength() >= $MAX_BITS){ - break; - } - - $this->bitBuffer->put(0x11, 8); - } - - } - - /** - * ECC masking - * - * ISO/IEC 18004:2000 Section 8.5 ff - * - * @see http://www.thonky.com/qr-code-tutorial/error-correction-coding - */ - 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, []); - $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; - - $bitBuffer = $this->bitBuffer->getBuffer(); - - 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 & $bitBuffer[$a + $offset]; - } - - [$num, $add] = $this->poly($key, $ecCount); - - foreach($this->ecdata[$key] as $c => $_){ - $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(array $arr, int $count) use (&$data, &$index, $rsCount):void{ - 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; - } - - /** - * helper method for the polynomial operations - */ - 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 deleted file mode 100644 index 93ad6221d..000000000 --- a/vendor/chillerlan/php-qrcode/src/Data/QRDataInterface.php +++ /dev/null @@ -1,200 +0,0 @@ -<?php -/** - * Interface QRDataInterface - * - * @filesource QRDataInterface.php - * @created 01.12.2015 - * @package chillerlan\QRCode\Data - * @author Smiley <smiley@chillerlan.net> - * @copyright 2015 Smiley - * @license MIT - */ - -namespace chillerlan\QRCode\Data; - -/** - * Specifies the methods reqired for the data modules (Number, Alphanum, Byte and Kanji) - * and holds version information in several constants - */ -interface QRDataInterface{ - - /** - * @var int[] - */ - const CHAR_MAP_NUMBER = [ - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, - ]; - - /** - * ISO/IEC 18004:2000 Table 5 - * - * @var int[] - */ - const CHAR_MAP_ALPHANUM = [ - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, - '8' => 8, '9' => 9, 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14, 'F' => 15, - 'G' => 16, 'H' => 17, 'I' => 18, 'J' => 19, 'K' => 20, 'L' => 21, 'M' => 22, 'N' => 23, - 'O' => 24, 'P' => 25, 'Q' => 26, 'R' => 27, 'S' => 28, 'T' => 29, 'U' => 30, 'V' => 31, - 'W' => 32, 'X' => 33, 'Y' => 34, 'Z' => 35, ' ' => 36, '$' => 37, '%' => 38, '*' => 39, - '+' => 40, '-' => 41, '.' => 42, '/' => 43, ':' => 44, - ]; - - /** - * ISO/IEC 18004:2000 Tables 7-11 - Number of symbol characters and input data capacity for versions 1 to 40 - * - * @see http://www.qrcode.com/en/about/version.html - * - * @var int [][][] - */ - 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 - ]; - - /** - * ISO/IEC 18004:2000 Tables 7-11 - Number of symbol characters and input data capacity for versions 1 to 40 - * - * @var int [][] - */ - 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], - ]; - - /** - * @see http://www.thonky.com/qr-code-tutorial/error-correction-table - * - * @var int [][][] - */ - 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) - */ - public function setData(string $data):QRDataInterface; - - /** - * returns a fresh matrix object with the data written for the given $maskPattern - */ - public function initMatrix(int $maskPattern, bool $test = null):QRMatrix; - -} diff --git a/vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php b/vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php new file mode 100644 index 000000000..94b93ac0e --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php @@ -0,0 +1,61 @@ +<?php +/** + * Class QRDataModeAbstract + * + * @created 19.11.2020 + * @author smiley <smiley@chillerlan.net> + * @copyright 2020 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\Mode; + +/** + * abstract methods for the several data modes + */ +abstract class QRDataModeAbstract implements QRDataModeInterface{ + + /** + * The data to write + */ + protected string $data; + + /** + * QRDataModeAbstract constructor. + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public function __construct(string $data){ + $data = $this::convertEncoding($data); + + if(!$this::validateString($data)){ + throw new QRCodeDataException('invalid data'); + } + + $this->data = $data; + } + + /** + * returns the character count of the $data string + */ + protected function getCharCount():int{ + return strlen($this->data); + } + + /** + * @inheritDoc + */ + public static function convertEncoding(string $string):string{ + return $string; + } + + /** + * shortcut + */ + protected static function getLengthBits(int $versionNumber):int{ + return Mode::getLengthBitsForVersion(static::DATAMODE, $versionNumber); + } + +} diff --git a/vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php b/vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php new file mode 100644 index 000000000..321cf60b6 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php @@ -0,0 +1,63 @@ +<?php +/** + * Interface QRDataModeInterface + * + * @created 01.12.2015 + * @author Smiley <smiley@chillerlan.net> + * @copyright 2015 Smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\BitBuffer; + +/** + * Specifies the methods reqired for the data modules (Number, Alphanum, Byte and Kanji) + */ +interface QRDataModeInterface{ + + /** + * the current data mode: Number, Alphanum, Kanji, Hanzi, Byte, ECI + * + * tbh I hate this constant here, but it's part of the interface, so I can't just declare it in the abstract class. + * (phan will complain about a PhanAccessOverridesFinalConstant) + * + * @see https://wiki.php.net/rfc/final_class_const + * + * @var int + * @see \chillerlan\QRCode\Common\Mode + * @internal do not call this constant from the interface, but rather from one of the child classes + */ + public const DATAMODE = -1; + + /** + * retruns the length in bits of the data string + */ + public function getLengthInBits():int; + + /** + * encoding conversion helper + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function convertEncoding(string $string):string; + + /** + * checks if the given string qualifies for the encoder module + */ + public static function validateString(string $string):bool; + + /** + * writes the actual data string to the BitBuffer, uses the given version to determine the length bits + * + * @see \chillerlan\QRCode\Data\QRData::writeBitBuffer() + */ + public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface; + + /** + * reads a segment from the BitBuffer and decodes in the current data mode + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string; + +} diff --git a/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php b/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php index 05c8b9069..e32633a99 100755 --- a/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php +++ b/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php @@ -2,9 +2,7 @@ /** * Class QRMatrix * - * @filesource QRMatrix.php * @created 15.11.2017 - * @package chillerlan\QRCode\Data * @author Smiley <smiley@chillerlan.net> * @copyright 2017 Smiley * @license MIT @@ -12,207 +10,124 @@ 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; +use chillerlan\QRCode\Common\{BitBuffer, EccLevel, MaskPattern, Version}; +use function array_fill, array_map, array_reverse, count, intdiv; /** - * Holds a numerical representation of the final QR Code; + * Holds an array representation of the final QR Code that contains numerical values for later output modifications; * maps the ECC coded binary data and applies the mask pattern * * @see http://www.thonky.com/qr-code-tutorial/format-version-information */ -final class QRMatrix{ +class QRMatrix{ + + /* + * special values + */ /** @var int */ - public const M_NULL = 0x00; + public const IS_DARK = 0b100000000000; + /** @var int */ + public const M_NULL = 0b000000000000; + /** @var int */ + public const M_LOGO = 0b001000000000; + /** @var int */ + public const M_LOGO_DARK = 0b101000000000; + + /* + * light values + */ + + /** @var int */ + public const M_DATA = 0b000000000010; + /** @var int */ + public const M_FINDER = 0b000000000100; + /** @var int */ + public const M_SEPARATOR = 0b000000001000; + /** @var int */ + public const M_ALIGNMENT = 0b000000010000; /** @var int */ - public const M_DARKMODULE = 0x02; + public const M_TIMING = 0b000000100000; /** @var int */ - public const M_DATA = 0x04; + public const M_FORMAT = 0b000001000000; /** @var int */ - public const M_FINDER = 0x06; + public const M_VERSION = 0b000010000000; /** @var int */ - public const M_SEPARATOR = 0x08; + public const M_QUIETZONE = 0b000100000000; + + /* + * dark values + */ + /** @var int */ - public const M_ALIGNMENT = 0x0a; + public const M_DARKMODULE = 0b100000000001; /** @var int */ - public const M_TIMING = 0x0c; + public const M_DATA_DARK = 0b100000000010; /** @var int */ - public const M_FORMAT = 0x0e; + public const M_FINDER_DARK = 0b100000000100; /** @var int */ - public const M_VERSION = 0x10; + public const M_ALIGNMENT_DARK = 0b100000010000; /** @var int */ - public const M_QUIETZONE = 0x12; + public const M_TIMING_DARK = 0b100000100000; /** @var int */ - public const M_LOGO = 0x14; + public const M_FORMAT_DARK = 0b100001000000; /** @var int */ - public const M_FINDER_DOT = 0x16; + public const M_VERSION_DARK = 0b100010000000; /** @var int */ - public const M_TEST = 0xff; + public const M_FINDER_DOT = 0b110000000000; - /** - * ISO/IEC 18004:2000 Annex E, Table E.1 - Row/column coordinates of center module of Alignment Patterns - * - * version -> pattern - * - * @var int[][] + /* + * values used for reversed reflectance */ - 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], - ]; - /** - * ISO/IEC 18004:2000 Annex D, Table D.1 - Version information bit stream for each version - * - * no version pattern for QR Codes < 7 - * - * @var int[] - */ - 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, - ]; + /** @var int */ + public const M_DARKMODULE_LIGHT = 0b000000000001; + /** @var int */ + public const M_FINDER_DOT_LIGHT = 0b010000000000; + /** @var int */ + public const M_SEPARATOR_DARK = 0b100000001000; + /** @var int */ + public const M_QUIETZONE_DARK = 0b100100000000; /** - * ISO/IEC 18004:2000 Section 8.9 - Format Information + * Map of flag => coord * - * ECC level -> mask pattern + * @see \chillerlan\QRCode\Data\QRMatrix::checkNeighbours() * - * @var int[][] + * @var array */ - 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, - ], + protected const neighbours = [ + 0b00000001 => [-1, -1], + 0b00000010 => [ 0, -1], + 0b00000100 => [ 1, -1], + 0b00001000 => [ 1, 0], + 0b00010000 => [ 1, 1], + 0b00100000 => [ 0, 1], + 0b01000000 => [-1, 1], + 0b10000000 => [-1, 0], ]; /** - * the current QR Code version number + * the matrix version - always set in QRMatrix, may be null in BitMatrix */ - protected int $version; + protected ?Version $version = null; /** - * the current ECC level + * the current ECC level - always set in QRMatrix, may be null in BitMatrix */ - protected int $eclevel; + protected ?EccLevel $eccLevel = null; /** - * the used mask pattern, set via QRMatrix::mapData() + * the mask pattern that was used in the most recent operation, set via: + * + * - QRMatrix::setFormatInfo() + * - QRMatrix::mask() + * - BitMatrix::readFormatInformation() */ - protected int $maskPattern = QRCode::MASK_PATTERN_AUTO; + protected ?MaskPattern $maskPattern = null; /** - * the size (side length) of the matrix + * the size (side length) of the matrix, including quiet zone (if created) */ protected int $moduleCount; @@ -225,37 +140,33 @@ final class QRMatrix{ /** * QRMatrix constructor. - * - * @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'); - } - + public function __construct(Version $version, EccLevel $eccLevel){ $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)); + $this->eccLevel = $eccLevel; + $this->moduleCount = $this->version->getDimension(); + $this->matrix = $this->createMatrix($this->moduleCount, $this::M_NULL); } /** - * shortcut to initialize the matrix + * Creates a 2-dimensional array (square) of the given $size */ - public function init(int $maskPattern, bool $test = null):QRMatrix{ + protected function createMatrix(int $size, int $value):array{ + return array_fill(0, $size, array_fill(0, $size, $value)); + } + + /** + * shortcut to initialize the functional patterns + */ + public function initFunctionalPatterns():self{ return $this ->setFinderPattern() ->setSeparators() ->setAlignmentPattern() ->setTimingPattern() - ->setVersionNumber($test) - ->setFormatInfo($maskPattern, $test) ->setDarkModule() + ->setVersionNumber() + ->setFormatInfo() ; } @@ -264,93 +175,245 @@ final class QRMatrix{ * * @return int[][]|bool[][] */ - public function matrix(bool $boolean = false):array{ + public function getMatrix(?bool $boolean = null):array{ - if(!$boolean){ + if($boolean !== true){ return $this->matrix; } - $matrix = []; + $matrix = $this->matrix; - foreach($this->matrix as $y => $row){ - $matrix[$y] = []; - - foreach($row as $x => $val){ - $matrix[$y][$x] = ($val >> 8) > 0; - } + foreach($matrix as &$row){ + $row = array_map([$this, 'isDark'], $row); } return $matrix; } /** + * @deprecated 5.0.0 use QRMatrix::getMatrix() instead + * @see \chillerlan\QRCode\Data\QRMatrix::getMatrix() + * @codeCoverageIgnore + */ + public function matrix(?bool $boolean = null):array{ + return $this->getMatrix($boolean); + } + + /** * Returns the current version number */ - public function version():int{ + public function getVersion():?Version{ return $this->version; } /** + * @deprecated 5.0.0 use QRMatrix::getVersion() instead + * @see \chillerlan\QRCode\Data\QRMatrix::getVersion() + * @codeCoverageIgnore + */ + public function version():?Version{ + return $this->getVersion(); + } + + /** * Returns the current ECC level */ - public function eccLevel():int{ - return $this->eclevel; + public function getEccLevel():?EccLevel{ + return $this->eccLevel; + } + + /** + * @deprecated 5.0.0 use QRMatrix::getEccLevel() instead + * @see \chillerlan\QRCode\Data\QRMatrix::getEccLevel() + * @codeCoverageIgnore + */ + public function eccLevel():?EccLevel{ + return $this->getEccLevel(); } /** * Returns the current mask pattern */ - public function maskPattern():int{ + public function getMaskPattern():?MaskPattern{ return $this->maskPattern; } /** + * @deprecated 5.0.0 use QRMatrix::getMaskPattern() instead + * @see \chillerlan\QRCode\Data\QRMatrix::getMaskPattern() + * @codeCoverageIgnore + */ + public function maskPattern():?MaskPattern{ + return $this->getMaskPattern(); + } + + /** * Returns the absoulute size of the matrix, including quiet zone (after setting it). * * size = version * 4 + 17 [ + 2 * quietzone size] */ - public function size():int{ + public function getSize():int{ return $this->moduleCount; } /** - * Returns the value of the module at position [$x, $y] + * @deprecated 5.0.0 use QRMatrix::getSize() instead + * @see \chillerlan\QRCode\Data\QRMatrix::getSize() + * @codeCoverageIgnore + */ + public function size():int{ + return $this->getSize(); + } + + /** + * Returns the value of the module at position [$x, $y] or -1 if the coordinate is outside the matrix */ public function get(int $x, int $y):int{ + + if(!isset($this->matrix[$y][$x])){ + return -1; + } + return $this->matrix[$y][$x]; } /** * Sets the $M_TYPE value for the module at position [$x, $y] * - * true => $M_TYPE << 8 + * true => $M_TYPE | 0x800 * false => $M_TYPE */ - public function set(int $x, int $y, bool $value, int $M_TYPE):QRMatrix{ - $this->matrix[$y][$x] = $M_TYPE << ($value ? 8 : 0); + public function set(int $x, int $y, bool $value, int $M_TYPE):self{ + + if(isset($this->matrix[$y][$x])){ + // we don't know whether the input is dark, so we remove the dark bit + $M_TYPE &= ~$this::IS_DARK; + + if($value === true){ + $M_TYPE |= $this::IS_DARK; + } + + $this->matrix[$y][$x] = $M_TYPE; + } return $this; } /** - * Checks whether a module is true (dark) or false (light) + * Fills an area of $width * $height, from the given starting point [$startX, $startY] (top left) with $value for $M_TYPE. + */ + public function setArea(int $startX, int $startY, int $width, int $height, bool $value, int $M_TYPE):self{ + + for($y = $startY; $y < ($startY + $height); $y++){ + for($x = $startX; $x < ($startX + $width); $x++){ + $this->set($x, $y, $value, $M_TYPE); + } + } + + return $this; + } + + /** + * Flips the value of the module at ($x, $y) + */ + public function flip(int $x, int $y):self{ + + if(isset($this->matrix[$y][$x])){ + $this->matrix[$y][$x] ^= $this::IS_DARK; + } + + return $this; + } + + /** + * Checks whether the module at ($x, $y) is of the given $M_TYPE * - * true => $value >> 8 === $M_TYPE - * $value >> 8 > 0 + * true => $value & $M_TYPE === $M_TYPE * - * false => $value === $M_TYPE - * $value >> 8 === 0 + * Also, returns false if the given coordinates are out of range. + */ + public function checkType(int $x, int $y, int $M_TYPE):bool{ + + if(isset($this->matrix[$y][$x])){ + return ($this->matrix[$y][$x] & $M_TYPE) === $M_TYPE; + } + + return false; + } + + /** + * Checks whether the module at ($x, $y) is in the given array of $M_TYPES, + * returns true if a match is found, otherwise false. + */ + public function checkTypeIn(int $x, int $y, array $M_TYPES):bool{ + + foreach($M_TYPES as $type){ + if($this->checkType($x, $y, $type)){ + return true; + } + } + + return false; + } + + /** + * Checks whether the module at ($x, $y) is true (dark) or false (light) + * + * Also, returns false if the given coordinates are out of range. */ public function check(int $x, int $y):bool{ - return ($this->matrix[$y][$x] >> 8) > 0; + + if(isset($this->matrix[$y][$x])){ + return $this->isDark($this->matrix[$y][$x]); + } + + return false; } + /** + * Checks whether the given $M_TYPE is a dark value + */ + public function isDark(int $M_TYPE):bool{ + return ($M_TYPE & $this::IS_DARK) === $this::IS_DARK; + } + + /** + * Checks the status of the neighbouring modules for the module at ($x, $y) and returns a bitmask with the results. + * + * The 8 flags of the bitmask represent the status of each of the neighbouring fields, + * starting with the lowest bit for top left, going clockwise: + * + * 0 1 2 + * 7 # 3 + * 6 5 4 + */ + public function checkNeighbours(int $x, int $y, ?int $M_TYPE = null):int{ + $bits = 0; + + foreach($this::neighbours as $bit => [$ix, $iy]){ + $ix += $x; + $iy += $y; + + // $M_TYPE is given, skip if the field is not the same type + if($M_TYPE !== null && !$this->checkType($ix, $iy, $M_TYPE)){ + continue; + } + + if($this->checkType($ix, $iy, $this::IS_DARK)){ + $bits |= $bit; + } + } + + return $bits; + } /** * Sets the "dark module", that is always on the same position 1x1px away from the bottom left finder + * + * 4 * version + 9 or moduleCount - 8 */ - public function setDarkModule():QRMatrix{ - $this->set(8, 4 * $this->version + 9, true, $this::M_DARKMODULE); + public function setDarkModule():self{ + $this->set(8, ($this->moduleCount - 8), true, $this::M_DARKMODULE); return $this; } @@ -360,31 +423,20 @@ final class QRMatrix{ * * ISO/IEC 18004:2000 Section 7.3.2 */ - public function setFinderPattern():QRMatrix{ + public function setFinderPattern():self{ $pos = [ [0, 0], // top left - [$this->moduleCount - 7, 0], // bottom left - [0, $this->moduleCount - 7], // top right + [($this->moduleCount - 7), 0], // top right + [0, ($this->moduleCount - 7)], // bottom left ]; 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); - } - } - } + $this + ->setArea( $c[0] , $c[1] , 7, 7, true, $this::M_FINDER) + ->setArea(($c[0] + 1), ($c[1] + 1), 5, 5, false, $this::M_FINDER) + ->setArea(($c[0] + 2), ($c[1] + 2), 3, 3, true, $this::M_FINDER_DOT) + ; } return $this; @@ -395,24 +447,25 @@ final class QRMatrix{ * * ISO/IEC 18004:2000 Section 7.3.3 */ - public function setSeparators():QRMatrix{ + public function setSeparators():self{ $h = [ [7, 0], - [$this->moduleCount - 8, 0], - [7, $this->moduleCount - 8], + [($this->moduleCount - 8), 0], + [7, ($this->moduleCount - 8)], ]; $v = [ [7, 7], - [$this->moduleCount - 1, 7], - [7, $this->moduleCount - 8], + [($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); + // phpcs:ignore + $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); } } @@ -425,23 +478,22 @@ final class QRMatrix{ * * ISO/IEC 18004:2000 Section 7.3.5 */ - public function setAlignmentPattern():QRMatrix{ + public function setAlignmentPattern():self{ + $alignmentPattern = $this->version->getAlignmentPattern(); - foreach($this::alignmentPattern[$this->version] as $y){ - foreach($this::alignmentPattern[$this->version] as $x){ + foreach($alignmentPattern as $y){ + foreach($alignmentPattern 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); - } - } + $this + ->setArea(($x - 2), ($y - 2), 5, 5, true, $this::M_ALIGNMENT) + ->setArea(($x - 1), ($y - 1), 3, 3, false, $this::M_ALIGNMENT) + ->set($x, $y, true, $this::M_ALIGNMENT) + ; } } @@ -455,15 +507,15 @@ final class QRMatrix{ * * ISO/IEC 18004:2000 Section 7.3.4 */ - public function setTimingPattern():QRMatrix{ + public function setTimingPattern():self{ - foreach(range(8, $this->moduleCount - 8 - 1) as $i){ + for($i = 8; $i < ($this->moduleCount - 8); $i++){ if($this->matrix[6][$i] !== $this::M_NULL || $this->matrix[$i][6] !== $this::M_NULL){ continue; } - $v = $i % 2 === 0; + $v = ($i % 2) === 0; $this->set($i, 6, $v, $this::M_TIMING); // h $this->set(6, $i, $v, $this::M_TIMING); // v @@ -477,15 +529,15 @@ final class QRMatrix{ * * ISO/IEC 18004:2000 Section 8.10 */ - public function setVersionNumber(bool $test = null):QRMatrix{ - $bits = $this::versionPattern[$this->version] ?? false; + public function setVersionNumber():self{ + $bits = $this->version->getVersionPattern(); - if($bits !== false){ + if($bits !== null){ for($i = 0; $i < 18; $i++){ - $a = (int)floor($i / 3); - $b = $i % 3 + $this->moduleCount - 8 - 3; - $v = !$test && (($bits >> $i) & 1) === 1; + $a = intdiv($i, 3); + $b = (($i % 3) + ($this->moduleCount - 8 - 3)); + $v = (($bits >> $i) & 1) === 1; $this->set($b, $a, $v, $this::M_VERSION); // ne $this->set($a, $b, $v, $this::M_VERSION); // sw @@ -497,40 +549,43 @@ final class QRMatrix{ } /** - * Draws the format info along the finder patterns + * Draws the format info along the finder patterns. If no $maskPattern, all format info modules will be set to false. * * ISO/IEC 18004:2000 Section 8.9 */ - public function setFormatInfo(int $maskPattern, bool $test = null):QRMatrix{ - $bits = $this::formatPattern[QRCode::ECC_MODES[$this->eclevel]][$maskPattern] ?? 0; + public function setFormatInfo(?MaskPattern $maskPattern = null):self{ + $this->maskPattern = $maskPattern; + $bits = 0; // sets all format fields to false (test mode) + + if($this->maskPattern instanceof MaskPattern){ + $bits = $this->eccLevel->getformatPattern($this->maskPattern); + } for($i = 0; $i < 15; $i++){ - $v = !$test && (($bits >> $i) & 1) === 1; + $v = (($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); + $this->set(8, ($i + 1), $v, $this::M_FORMAT); } else{ - $this->set(8, $this->moduleCount - 15 + $i, $v, $this::M_FORMAT); + $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); + $this->set(($this->moduleCount - $i - 1), 8, $v, $this::M_FORMAT); } elseif($i < 9){ - $this->set(15 - $i, 8, $v, $this::M_FORMAT); + $this->set(((15 - $i)), 8, $v, $this::M_FORMAT); } else{ - $this->set(15 - $i - 1, 8, $v, $this::M_FORMAT); + $this->set((15 - $i - 1), 8, $v, $this::M_FORMAT); } } - $this->set(8, $this->moduleCount - 8, !$test, $this::M_FORMAT); - return $this; } @@ -541,30 +596,62 @@ final class QRMatrix{ * * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - public function setQuietZone(int $size = null):QRMatrix{ + public function setQuietZone(int $quietZoneSize):self{ + + // early exit if there's nothing to add + if($quietZoneSize < 1){ + return $this; + } - if($this->matrix[$this->moduleCount - 1][$this->moduleCount - 1] === $this::M_NULL){ + 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; + // create a matrix with the new size + $newSize = ($this->moduleCount + ($quietZoneSize * 2)); + $newMatrix = $this->createMatrix($newSize, $this::M_QUIETZONE); - 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); + // copy over the current matrix + foreach($this->matrix as $y => $row){ + foreach($row as $x => $val){ + $newMatrix[($y + $quietZoneSize)][($x + $quietZoneSize)] = $val; } } - $this->moduleCount += ($size * 2); + // set the new values + $this->moduleCount = $newSize; + $this->matrix = $newMatrix; + + return $this; + } - $r = array_fill(0, $this->moduleCount, $this::M_QUIETZONE); + /** + * Rotates the matrix by 90 degrees clock wise + */ + public function rotate90():self{ + /** @phan-suppress-next-line PhanParamTooFewInternalUnpack */ + $this->matrix = array_map((fn(int ...$a):array => array_reverse($a)), ...$this->matrix); - for($i = 0; $i < $size; $i++){ - array_unshift($this->matrix, $r); - array_push($this->matrix, $r); + return $this; + } + + /** + * Inverts the values of the whole matrix + * + * ISO/IEC 18004:2015 Section 6.2 - Reflectance reversal + */ + public function invert():self{ + + foreach($this->matrix as $y => $row){ + foreach($row as $x => $val){ + + // skip null fields + if($val === $this::M_NULL){ + continue; + } + + $this->flip($x, $y); + } } return $this; @@ -572,11 +659,15 @@ final class QRMatrix{ /** * Clears a space of $width * $height in order to add a logo or text. + * If no $height is given, the space will be assumed a square of $width. * - * 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. + * Additionally, the logo space can be positioned within the QR Code 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. * + * The coordinates of $startX and $startY do not include the quiet zone: + * [0, 0] is always the top left module of the top left finder pattern, negative values go into the quiet zone top and left. + * * 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. @@ -588,13 +679,27 @@ final class QRMatrix{ * * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - public function setLogoSpace(int $width, int $height, int $startX = null, int $startY = null):QRMatrix{ + public function setLogoSpace(int $width, ?int $height = null, ?int $startX = null, ?int $startY = null):self{ + $height ??= $width; - // for logos we operate in ECC H (30%) only - if($this->eclevel !== QRCode::ECC_H){ + // if width and height happen to be negative or 0 (default value), just return - nothing to do + if($width <= 0 || $height <= 0){ + return $this; // @codeCoverageIgnore + } + + // for logos, we operate in ECC H (30%) only + if($this->eccLevel->getLevel() !== EccLevel::H){ throw new QRCodeDataException('ECC level "H" required to add logo space'); } + // $this->moduleCount includes the quiet zone (if created), we need the QR size here + $dimension = $this->version->getDimension(); + + // throw if the size exceeds the qrcode size + if($width > $dimension || $height > $dimension){ + throw new QRCodeDataException('logo dimensions exceed matrix size'); + } + // we need uneven sizes to center the logo space, adjust if needed if($startX === null && ($width % 2) === 0){ $width++; @@ -604,36 +709,29 @@ final class QRMatrix{ $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)){ + if(($width * $height) > (int)($dimension * $dimension * 0.25)){ 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; + $quietzone = (($this->moduleCount - $dimension) / 2); + $end = ($this->moduleCount - $quietzone); // determine start coordinates - $startX = ($startX !== null ? $startX : ($length - $width) / 2) + $qz; - $startY = ($startY !== null ? $startY : ($length - $height) / 2) + $qz; + $startX ??= (($dimension - $width) / 2); + $startY ??= (($dimension - $height) / 2); + $endX = ($quietzone + $startX + $width); + $endY = ($quietzone + $startY + $height); // clear the space - foreach($this->matrix as $y => $row){ - foreach($row as $x => $val){ + for($y = ($quietzone + $startY); $y < $endY; $y++){ + for($x = ($quietzone + $startX); $x < $endX; $x++){ // out of bounds, skip - if($x < $start || $y < $start ||$x >= $end || $y >= $end){ + if($x < $quietzone || $y < $quietzone ||$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); - } + + $this->set($x, $y, false, $this::M_LOGO); } } @@ -641,100 +739,75 @@ final class QRMatrix{ } /** - * Maps the binary $data array from QRDataInterface::maskECC() on the matrix, - * masking the data using $maskPattern (ISO/IEC 18004:2000 Section 8.8) - * - * @see \chillerlan\QRCode\Data\QRDataAbstract::maskECC() - * - * @param int[] $data - * @param int $maskPattern - * - * @return \chillerlan\QRCode\Data\QRMatrix + * Maps the interleaved binary $data on the matrix */ - public function mapData(array $data, int $maskPattern):QRMatrix{ - $this->maskPattern = $maskPattern; - $byteCount = count($data); - $y = $this->moduleCount - 1; - $inc = -1; - $byteIndex = 0; - $bitIndex = 7; - $mask = $this->getMask($this->maskPattern); + public function writeCodewords(BitBuffer $bitBuffer):self{ + $data = (new ReedSolomonEncoder($this->version, $this->eccLevel))->interleaveEcBytes($bitBuffer); + $byteCount = count($data); + $iByte = 0; + $iBit = 7; + $direction = true; - for($i = $y; $i > 0; $i -= 2){ + for($i = ($this->moduleCount - 1); $i > 0; $i -= 2){ + // skip vertical alignment pattern 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; - } + for($count = 0; $count < $this->moduleCount; $count++){ + $y = $count; - $this->matrix[$y][$x] = $this::M_DATA << ($v ? 8 : 0); - $bitIndex--; + if($direction){ + $y = ($this->moduleCount - 1 - $count); + } - if($bitIndex === -1){ - $byteIndex++; - $bitIndex = 7; - } + for($col = 0; $col < 2; $col++){ + $x = ($i - $col); + // skip functional patterns + if($this->matrix[$y][$x] !== $this::M_NULL){ + continue; } - } - $y += $inc; + $this->matrix[$y][$x] = $this::M_DATA; - if($y < 0 || $this->moduleCount <= $y){ - $y -= $inc; - $inc = -$inc; + if($iByte < $byteCount && (($data[$iByte] >> $iBit--) & 1) === 1){ + $this->matrix[$y][$x] |= $this::IS_DARK; + } - break; + if($iBit === -1){ + $iByte++; + $iBit = 7; + } } - } + + $direction = !$direction; // switch directions } 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() + * Applies/reverses the mask pattern * - * @internal - * - * @throws \chillerlan\QRCode\Data\QRCodeDataException + * ISO/IEC 18004:2000 Section 8.8.1 */ - protected function getMask(int $maskPattern):Closure{ + public function mask(MaskPattern $maskPattern):self{ + $this->maskPattern = $maskPattern; + $mask = $this->maskPattern->getMask(); - if((0b111 & $maskPattern) !== $maskPattern){ - throw new QRCodeDataException('invalid mask pattern'); // @codeCoverageIgnore + foreach($this->matrix as $y => $row){ + foreach($row as $x => $val){ + // skip non-data modules + if(($val & $this::M_DATA) === $this::M_DATA && $mask($x, $y)){ + $this->flip($x, $y); + } + } } - return [ - 0b000 => fn($x, $y):int => ($x + $y) % 2, - 0b001 => fn($x, $y):int => $y % 2, - 0b010 => fn($x, $y):int => $x % 3, - 0b011 => fn($x, $y):int => ($x + $y) % 3, - 0b100 => fn($x, $y):int => ((int)($y / 2) + (int)($x / 3)) % 2, - 0b101 => fn($x, $y):int => (($x * $y) % 2) + (($x * $y) % 3), - 0b110 => fn($x, $y):int => ((($x * $y) % 2) + (($x * $y) % 3)) % 2, - 0b111 => fn($x, $y):int => ((($x * $y) % 3) + (($x + $y) % 2)) % 2, - ][$maskPattern]; + return $this; } } diff --git a/vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php b/vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php new file mode 100644 index 000000000..60444378c --- /dev/null +++ b/vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php @@ -0,0 +1,127 @@ +<?php +/** + * Class ReedSolomonEncoder + * + * @created 07.01.2021 + * @author smiley <smiley@chillerlan.net> + * @copyright 2021 smiley + * @license MIT + */ + +namespace chillerlan\QRCode\Data; + +use chillerlan\QRCode\Common\{BitBuffer, EccLevel, GenericGFPoly, GF256, Version}; +use function array_fill, array_merge, count, max; + +/** + * Reed-Solomon encoding - ISO/IEC 18004:2000 Section 8.5 ff + * + * @see http://www.thonky.com/qr-code-tutorial/error-correction-coding + */ +final class ReedSolomonEncoder{ + + private Version $version; + private EccLevel $eccLevel; + + private array $interleavedData; + private int $interleavedDataIndex; + + /** + * ReedSolomonDecoder constructor + */ + public function __construct(Version $version, EccLevel $eccLevel){ + $this->version = $version; + $this->eccLevel = $eccLevel; + } + + /** + * ECC encoding and interleaving + * + * @throws \chillerlan\QRCode\QRCodeException + */ + public function interleaveEcBytes(BitBuffer $bitBuffer):array{ + [$numEccCodewords, [[$l1, $b1], [$l2, $b2]]] = $this->version->getRSBlocks($this->eccLevel); + + $rsBlocks = array_fill(0, $l1, [($numEccCodewords + $b1), $b1]); + + if($l2 > 0){ + $rsBlocks = array_merge($rsBlocks, array_fill(0, $l2, [($numEccCodewords + $b2), $b2])); + } + + $bitBufferData = $bitBuffer->getBuffer(); + $dataBytes = []; + $ecBytes = []; + $maxDataBytes = 0; + $maxEcBytes = 0; + $dataByteOffset = 0; + + foreach($rsBlocks as $key => [$rsBlockTotal, $dataByteCount]){ + $dataBytes[$key] = []; + + for($i = 0; $i < $dataByteCount; $i++){ + $dataBytes[$key][$i] = ($bitBufferData[($i + $dataByteOffset)] & 0xff); + } + + $ecByteCount = ($rsBlockTotal - $dataByteCount); + $ecBytes[$key] = $this->encode($dataBytes[$key], $ecByteCount); + $maxDataBytes = max($maxDataBytes, $dataByteCount); + $maxEcBytes = max($maxEcBytes, $ecByteCount); + $dataByteOffset += $dataByteCount; + } + + $this->interleavedData = array_fill(0, $this->version->getTotalCodewords(), 0); + $this->interleavedDataIndex = 0; + $numRsBlocks = ($l1 + $l2); + + $this->interleave($dataBytes, $maxDataBytes, $numRsBlocks); + $this->interleave($ecBytes, $maxEcBytes, $numRsBlocks); + + return $this->interleavedData; + } + + /** + * + */ + private function encode(array $dataBytes, int $ecByteCount):array{ + $rsPoly = new GenericGFPoly([1]); + + for($i = 0; $i < $ecByteCount; $i++){ + $rsPoly = $rsPoly->multiply(new GenericGFPoly([1, GF256::exp($i)])); + } + + $rsPolyDegree = $rsPoly->getDegree(); + + $modCoefficients = (new GenericGFPoly($dataBytes, $rsPolyDegree)) + ->mod($rsPoly) + ->getCoefficients() + ; + + $ecBytes = array_fill(0, $rsPolyDegree, 0); + $count = (count($modCoefficients) - $rsPolyDegree); + + foreach($ecBytes as $i => &$val){ + $modIndex = ($i + $count); + $val = 0; + + if($modIndex >= 0){ + $val = $modCoefficients[$modIndex]; + } + } + + return $ecBytes; + } + + /** + * + */ + private function interleave(array $byteArray, int $maxBytes, int $numRsBlocks):void{ + for($x = 0; $x < $maxBytes; $x++){ + for($y = 0; $y < $numRsBlocks; $y++){ + if($x < count($byteArray[$y])){ + $this->interleavedData[$this->interleavedDataIndex++] = $byteArray[$y][$x]; + } + } + } + } + +} |