aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/chillerlan/php-qrcode/src
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/chillerlan/php-qrcode/src')
-rw-r--r--vendor/chillerlan/php-qrcode/src/Common/BitBuffer.php180
-rw-r--r--vendor/chillerlan/php-qrcode/src/Common/ECICharset.php125
-rw-r--r--vendor/chillerlan/php-qrcode/src/Common/EccLevel.php223
-rw-r--r--vendor/chillerlan/php-qrcode/src/Common/GDLuminanceSource.php97
-rw-r--r--vendor/chillerlan/php-qrcode/src/Common/GF256.php154
-rw-r--r--vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php263
-rw-r--r--vendor/chillerlan/php-qrcode/src/Common/IMagickLuminanceSource.php78
-rw-r--r--vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceAbstract.php107
-rw-r--r--vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceInterface.php61
-rw-r--r--vendor/chillerlan/php-qrcode/src/Common/MaskPattern.php329
-rw-r--r--vendor/chillerlan/php-qrcode/src/Common/Mode.php96
-rw-r--r--vendor/chillerlan/php-qrcode/src/Common/Version.php287
-rw-r--r--vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php116
-rw-r--r--vendor/chillerlan/php-qrcode/src/Data/Byte.php67
-rw-r--r--vendor/chillerlan/php-qrcode/src/Data/ECI.php165
-rw-r--r--vendor/chillerlan/php-qrcode/src/Data/Hanzi.php206
-rw-r--r--vendor/chillerlan/php-qrcode/src/Data/Kanji.php171
-rw-r--r--vendor/chillerlan/php-qrcode/src/Data/MaskPatternTester.php203
-rw-r--r--vendor/chillerlan/php-qrcode/src/Data/Number.php135
-rw-r--r--vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php9
-rw-r--r--vendor/chillerlan/php-qrcode/src/Data/QRData.php263
-rw-r--r--vendor/chillerlan/php-qrcode/src/Data/QRDataAbstract.php311
-rw-r--r--vendor/chillerlan/php-qrcode/src/Data/QRDataInterface.php200
-rw-r--r--vendor/chillerlan/php-qrcode/src/Data/QRDataModeAbstract.php61
-rw-r--r--vendor/chillerlan/php-qrcode/src/Data/QRDataModeInterface.php63
-rwxr-xr-xvendor/chillerlan/php-qrcode/src/Data/QRMatrix.php811
-rw-r--r--vendor/chillerlan/php-qrcode/src/Data/ReedSolomonEncoder.php127
-rw-r--r--vendor/chillerlan/php-qrcode/src/Decoder/Binarizer.php361
-rw-r--r--vendor/chillerlan/php-qrcode/src/Decoder/BitMatrix.php430
-rw-r--r--vendor/chillerlan/php-qrcode/src/Decoder/Decoder.php173
-rw-r--r--vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php99
-rw-r--r--vendor/chillerlan/php-qrcode/src/Decoder/QRCodeDecoderException.php20
-rw-r--r--vendor/chillerlan/php-qrcode/src/Decoder/ReedSolomonDecoder.php313
-rw-r--r--vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php34
-rw-r--r--vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php284
-rw-r--r--vendor/chillerlan/php-qrcode/src/Detector/Detector.php350
-rw-r--r--vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php92
-rw-r--r--vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php773
-rw-r--r--vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php181
-rw-r--r--vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php182
-rw-r--r--vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php20
-rw-r--r--vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php73
-rw-r--r--vendor/chillerlan/php-qrcode/src/Helpers/BitBuffer.php89
-rw-r--r--vendor/chillerlan/php-qrcode/src/Helpers/Polynomial.php178
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php9
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QREps.php173
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php150
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php400
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php33
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php33
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php40
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php33
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php33
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QRImage.php210
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QRImagick.php206
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php174
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php51
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php200
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php230
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php222
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QRString.php91
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php67
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QRStringText.php76
-rwxr-xr-xvendor/chillerlan/php-qrcode/src/QRCode.php551
-rw-r--r--vendor/chillerlan/php-qrcode/src/QRCodeException.php6
-rw-r--r--vendor/chillerlan/php-qrcode/src/QROptions.php34
-rw-r--r--vendor/chillerlan/php-qrcode/src/QROptionsTrait.php648
67 files changed, 9921 insertions, 2309 deletions
diff --git a/vendor/chillerlan/php-qrcode/src/Common/BitBuffer.php b/vendor/chillerlan/php-qrcode/src/Common/BitBuffer.php
new file mode 100644
index 000000000..4a59f2b58
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Common/BitBuffer.php
@@ -0,0 +1,180 @@
+<?php
+/**
+ * Class BitBuffer
+ *
+ * @created 25.11.2015
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2015 Smiley
+ * @license MIT
+ */
+
+namespace chillerlan\QRCode\Common;
+
+use chillerlan\QRCode\QRCodeException;
+use function count, floor, min;
+
+/**
+ * Holds the raw binary data
+ */
+final class BitBuffer{
+
+ /**
+ * The buffer content
+ *
+ * @var int[]
+ */
+ private array $buffer;
+
+ /**
+ * Length of the content (bits)
+ */
+ private int $length;
+
+ /**
+ * Read count (bytes)
+ */
+ private int $bytesRead = 0;
+
+ /**
+ * Read count (bits)
+ */
+ private int $bitsRead = 0;
+
+ /**
+ * BitBuffer constructor.
+ *
+ * @param int[] $bytes
+ */
+ public function __construct(array $bytes = []){
+ $this->buffer = $bytes;
+ $this->length = count($this->buffer);
+ }
+
+ /**
+ * appends a sequence of bits
+ */
+ public function put(int $bits, int $length):self{
+
+ for($i = 0; $i < $length; $i++){
+ $this->putBit((($bits >> ($length - $i - 1)) & 1) === 1);
+ }
+
+ return $this;
+ }
+
+ /**
+ * appends a single bit
+ */
+ public function putBit(bool $bit):self{
+ $bufIndex = (int)floor($this->length / 8);
+
+ if(count($this->buffer) <= $bufIndex){
+ $this->buffer[] = 0;
+ }
+
+ if($bit === true){
+ $this->buffer[$bufIndex] |= (0x80 >> ($this->length % 8));
+ }
+
+ $this->length++;
+
+ return $this;
+ }
+
+ /**
+ * returns the current buffer length
+ */
+ public function getLength():int{
+ return $this->length;
+ }
+
+ /**
+ * returns the buffer content
+ *
+ * to debug: array_map(fn($v) => sprintf('%08b', $v), $bitBuffer->getBuffer())
+ */
+ public function getBuffer():array{
+ return $this->buffer;
+ }
+
+ /**
+ * @return int number of bits that can be read successfully
+ */
+ public function available():int{
+ return ((8 * ($this->length - $this->bytesRead)) - $this->bitsRead);
+ }
+
+ /**
+ * @author Sean Owen, ZXing
+ *
+ * @param int $numBits number of bits to read
+ *
+ * @return int representing the bits read. The bits will appear as the least-significant bits of the int
+ * @throws \chillerlan\QRCode\QRCodeException if numBits isn't in [1,32] or more than is available
+ */
+ public function read(int $numBits):int{
+
+ if($numBits < 1 || $numBits > $this->available()){
+ throw new QRCodeException('invalid $numBits: '.$numBits);
+ }
+
+ $result = 0;
+
+ // First, read remainder from current byte
+ if($this->bitsRead > 0){
+ $bitsLeft = (8 - $this->bitsRead);
+ $toRead = min($numBits, $bitsLeft);
+ $bitsToNotRead = ($bitsLeft - $toRead);
+ $mask = ((0xff >> (8 - $toRead)) << $bitsToNotRead);
+ $result = (($this->buffer[$this->bytesRead] & $mask) >> $bitsToNotRead);
+ $numBits -= $toRead;
+ $this->bitsRead += $toRead;
+
+ if($this->bitsRead === 8){
+ $this->bitsRead = 0;
+ $this->bytesRead++;
+ }
+ }
+
+ // Next read whole bytes
+ if($numBits > 0){
+
+ while($numBits >= 8){
+ $result = (($result << 8) | ($this->buffer[$this->bytesRead] & 0xff));
+ $this->bytesRead++;
+ $numBits -= 8;
+ }
+
+ // Finally read a partial byte
+ if($numBits > 0){
+ $bitsToNotRead = (8 - $numBits);
+ $mask = ((0xff >> $bitsToNotRead) << $bitsToNotRead);
+ $result = (($result << $numBits) | (($this->buffer[$this->bytesRead] & $mask) >> $bitsToNotRead));
+ $this->bitsRead += $numBits;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Clears the buffer and resets the stats
+ */
+ public function clear():self{
+ $this->buffer = [];
+ $this->length = 0;
+
+ return $this->rewind();
+ }
+
+ /**
+ * Resets the read-counters
+ */
+ public function rewind():self{
+ $this->bytesRead = 0;
+ $this->bitsRead = 0;
+
+ return $this;
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Common/ECICharset.php b/vendor/chillerlan/php-qrcode/src/Common/ECICharset.php
new file mode 100644
index 000000000..0c98e36da
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Common/ECICharset.php
@@ -0,0 +1,125 @@
+<?php
+/**
+ * Class ECICharset
+ *
+ * @created 21.01.2021
+ * @author ZXing Authors
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2021 smiley
+ * @license Apache-2.0
+ */
+
+namespace chillerlan\QRCode\Common;
+
+use chillerlan\QRCode\QRCodeException;
+use function sprintf;
+
+/**
+ * ISO/IEC 18004:2000 - 8.4.1 Extended Channel Interpretation (ECI) Mode
+ */
+final class ECICharset{
+
+ public const CP437 = 0; // Code page 437, DOS Latin US
+ public const ISO_IEC_8859_1_GLI = 1; // GLI encoding with characters 0 to 127 identical to ISO/IEC 646 and characters 128 to 255 identical to ISO 8859-1
+ public const CP437_WO_GLI = 2; // An equivalent code table to CP437, without the return-to-GLI 0 logic
+ public const ISO_IEC_8859_1 = 3; // Latin-1 (Default)
+ public const ISO_IEC_8859_2 = 4; // Latin-2
+ public const ISO_IEC_8859_3 = 5; // Latin-3
+ public const ISO_IEC_8859_4 = 6; // Latin-4
+ public const ISO_IEC_8859_5 = 7; // Latin/Cyrillic
+ public const ISO_IEC_8859_6 = 8; // Latin/Arabic
+ public const ISO_IEC_8859_7 = 9; // Latin/Greek
+ public const ISO_IEC_8859_8 = 10; // Latin/Hebrew
+ public const ISO_IEC_8859_9 = 11; // Latin-5
+ public const ISO_IEC_8859_10 = 12; // Latin-6
+ public const ISO_IEC_8859_11 = 13; // Latin/Thai
+ // 14 reserved
+ public const ISO_IEC_8859_13 = 15; // Latin-7 (Baltic Rim)
+ public const ISO_IEC_8859_14 = 16; // Latin-8 (Celtic)
+ public const ISO_IEC_8859_15 = 17; // Latin-9
+ public const ISO_IEC_8859_16 = 18; // Latin-10
+ // 19 reserved
+ public const SHIFT_JIS = 20; // JIS X 0208 Annex 1 + JIS X 0201
+ public const WINDOWS_1250_LATIN_2 = 21; // Superset of Latin-2, Central Europe
+ public const WINDOWS_1251_CYRILLIC = 22; // Latin/Cyrillic
+ public const WINDOWS_1252_LATIN_1 = 23; // Superset of Latin-1
+ public const WINDOWS_1256_ARABIC = 24;
+ public const ISO_IEC_10646_UCS_2 = 25; // High order byte first (UTF-16BE)
+ public const ISO_IEC_10646_UTF_8 = 26; // UTF-8
+ public const ISO_IEC_646_1991 = 27; // International Reference Version of ISO 7-bit coded character set (US-ASCII)
+ public const BIG5 = 28; // Big 5 (Taiwan) Chinese Character Set
+ public const GB18030 = 29; // GB (PRC) Chinese Character Set
+ public const EUC_KR = 30; // Korean Character Set
+
+ /**
+ * map of charset id -> name
+ *
+ * @see \mb_list_encodings()
+ */
+ public const MB_ENCODINGS = [
+ self::CP437 => null,
+ self::ISO_IEC_8859_1_GLI => null,
+ self::CP437_WO_GLI => null,
+ self::ISO_IEC_8859_1 => 'ISO-8859-1',
+ self::ISO_IEC_8859_2 => 'ISO-8859-2',
+ self::ISO_IEC_8859_3 => 'ISO-8859-3',
+ self::ISO_IEC_8859_4 => 'ISO-8859-4',
+ self::ISO_IEC_8859_5 => 'ISO-8859-5',
+ self::ISO_IEC_8859_6 => 'ISO-8859-6',
+ self::ISO_IEC_8859_7 => 'ISO-8859-7',
+ self::ISO_IEC_8859_8 => 'ISO-8859-8',
+ self::ISO_IEC_8859_9 => 'ISO-8859-9',
+ self::ISO_IEC_8859_10 => 'ISO-8859-10',
+ self::ISO_IEC_8859_11 => null,
+ self::ISO_IEC_8859_13 => 'ISO-8859-13',
+ self::ISO_IEC_8859_14 => 'ISO-8859-14',
+ self::ISO_IEC_8859_15 => 'ISO-8859-15',
+ self::ISO_IEC_8859_16 => 'ISO-8859-16',
+ self::SHIFT_JIS => 'SJIS',
+ self::WINDOWS_1250_LATIN_2 => null, // @see https://www.php.net/manual/en/function.mb-convert-encoding.php#112547
+ self::WINDOWS_1251_CYRILLIC => 'Windows-1251',
+ self::WINDOWS_1252_LATIN_1 => 'Windows-1252',
+ self::WINDOWS_1256_ARABIC => null, // @see https://stackoverflow.com/a/8592995
+ self::ISO_IEC_10646_UCS_2 => 'UTF-16BE',
+ self::ISO_IEC_10646_UTF_8 => 'UTF-8',
+ self::ISO_IEC_646_1991 => 'ASCII',
+ self::BIG5 => 'BIG-5',
+ self::GB18030 => 'GB18030',
+ self::EUC_KR => 'EUC-KR',
+ ];
+
+ /**
+ * The current ECI character set ID
+ */
+ private int $charsetID;
+
+ /**
+ * @throws \chillerlan\QRCode\QRCodeException
+ */
+ public function __construct(int $charsetID){
+
+ if($charsetID < 0 || $charsetID > 999999){
+ throw new QRCodeException(sprintf('invalid charset id: "%s"', $charsetID));
+ }
+
+ $this->charsetID = $charsetID;
+ }
+
+ /**
+ * Returns the current character set ID
+ */
+ public function getID():int{
+ return $this->charsetID;
+ }
+
+ /**
+ * Returns the name of the current character set or null if no name is available
+ *
+ * @see \mb_convert_encoding()
+ * @see \iconv()
+ */
+ public function getName():?string{
+ return (self::MB_ENCODINGS[$this->charsetID] ?? null);
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Common/EccLevel.php b/vendor/chillerlan/php-qrcode/src/Common/EccLevel.php
new file mode 100644
index 000000000..789d7f79d
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Common/EccLevel.php
@@ -0,0 +1,223 @@
+<?php
+/**
+ * Class EccLevel
+ *
+ * @created 19.11.2020
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2020 smiley
+ * @license MIT
+ */
+
+namespace chillerlan\QRCode\Common;
+
+use chillerlan\QRCode\QRCodeException;
+use function array_column;
+
+/**
+ * This class encapsulates the four error correction levels defined by the QR code standard.
+ */
+final class EccLevel{
+
+ // ISO/IEC 18004:2000 Tables 12, 25
+
+ /** @var int */
+ public const L = 0b01; // 7%.
+ /** @var int */
+ public const M = 0b00; // 15%.
+ /** @var int */
+ public const Q = 0b11; // 25%.
+ /** @var int */
+ public const H = 0b10; // 30%.
+
+ /**
+ * ISO/IEC 18004:2000 Tables 7-11 - Number of symbol characters and input data capacity for versions 1 to 40
+ *
+ * @var int[][]
+ */
+ private const MAX_BITS = [
+ // [ L, M, Q, H] // v => modules
+ [ 0, 0, 0, 0], // 0 => will be ignored, index starts at 1
+ [ 152, 128, 104, 72], // 1 => 21
+ [ 272, 224, 176, 128], // 2 => 25
+ [ 440, 352, 272, 208], // 3 => 29
+ [ 640, 512, 384, 288], // 4 => 33
+ [ 864, 688, 496, 368], // 5 => 37
+ [ 1088, 864, 608, 480], // 6 => 41
+ [ 1248, 992, 704, 528], // 7 => 45
+ [ 1552, 1232, 880, 688], // 8 => 49
+ [ 1856, 1456, 1056, 800], // 9 => 53
+ [ 2192, 1728, 1232, 976], // 10 => 57
+ [ 2592, 2032, 1440, 1120], // 11 => 61
+ [ 2960, 2320, 1648, 1264], // 12 => 65
+ [ 3424, 2672, 1952, 1440], // 13 => 69 NICE!
+ [ 3688, 2920, 2088, 1576], // 14 => 73
+ [ 4184, 3320, 2360, 1784], // 15 => 77
+ [ 4712, 3624, 2600, 2024], // 16 => 81
+ [ 5176, 4056, 2936, 2264], // 17 => 85
+ [ 5768, 4504, 3176, 2504], // 18 => 89
+ [ 6360, 5016, 3560, 2728], // 19 => 93
+ [ 6888, 5352, 3880, 3080], // 20 => 97
+ [ 7456, 5712, 4096, 3248], // 21 => 101
+ [ 8048, 6256, 4544, 3536], // 22 => 105
+ [ 8752, 6880, 4912, 3712], // 23 => 109
+ [ 9392, 7312, 5312, 4112], // 24 => 113
+ [10208, 8000, 5744, 4304], // 25 => 117
+ [10960, 8496, 6032, 4768], // 26 => 121
+ [11744, 9024, 6464, 5024], // 27 => 125
+ [12248, 9544, 6968, 5288], // 28 => 129
+ [13048, 10136, 7288, 5608], // 29 => 133
+ [13880, 10984, 7880, 5960], // 30 => 137
+ [14744, 11640, 8264, 6344], // 31 => 141
+ [15640, 12328, 8920, 6760], // 32 => 145
+ [16568, 13048, 9368, 7208], // 33 => 149
+ [17528, 13800, 9848, 7688], // 34 => 153
+ [18448, 14496, 10288, 7888], // 35 => 157
+ [19472, 15312, 10832, 8432], // 36 => 161
+ [20528, 15936, 11408, 8768], // 37 => 165
+ [21616, 16816, 12016, 9136], // 38 => 169
+ [22496, 17728, 12656, 9776], // 39 => 173
+ [23648, 18672, 13328, 10208], // 40 => 177
+ ];
+
+ /**
+ * ISO/IEC 18004:2000 Section 8.9 - Format Information
+ *
+ * ECC level -> mask pattern
+ *
+ * @var int[][]
+ */
+ private const FORMAT_PATTERN = [
+ [ // 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,
+ ],
+ ];
+
+ /**
+ * The current ECC level value
+ *
+ * L: 0b01
+ * M: 0b00
+ * Q: 0b11
+ * H: 0b10
+ */
+ private int $eccLevel;
+
+ /**
+ * @param int $eccLevel containing the two bits encoding a QR Code's error correction level
+ *
+ * @todo: accept string values (PHP8+)
+ * @see https://github.com/chillerlan/php-qrcode/discussions/160
+ *
+ * @throws \chillerlan\QRCode\QRCodeException
+ */
+ public function __construct(int $eccLevel){
+
+ if((0b11 & $eccLevel) !== $eccLevel){
+ throw new QRCodeException('invalid ECC level');
+ }
+
+ $this->eccLevel = $eccLevel;
+ }
+
+ /**
+ * returns the string representation of the current ECC level
+ */
+ public function __toString():string{
+ return [
+ self::L => 'L',
+ self::M => 'M',
+ self::Q => 'Q',
+ self::H => 'H',
+ ][$this->eccLevel];
+ }
+
+ /**
+ * returns the current ECC level
+ */
+ public function getLevel():int{
+ return $this->eccLevel;
+ }
+
+ /**
+ * returns the ordinal value of the current ECC level
+ *
+ * references to the keys of the following tables:
+ *
+ * @see \chillerlan\QRCode\Common\EccLevel::MAX_BITS
+ * @see \chillerlan\QRCode\Common\EccLevel::FORMAT_PATTERN
+ * @see \chillerlan\QRCode\Common\Version::RSBLOCKS
+ */
+ public function getOrdinal():int{
+ return [
+ self::L => 0,
+ self::M => 1,
+ self::Q => 2,
+ self::H => 3,
+ ][$this->eccLevel];
+ }
+
+ /**
+ * returns the format pattern for the given $eccLevel and $maskPattern
+ */
+ public function getformatPattern(MaskPattern $maskPattern):int{
+ return self::FORMAT_PATTERN[$this->getOrdinal()][$maskPattern->getPattern()];
+ }
+
+ /**
+ * returns an array with the max bit lengths for version 1-40 and the current ECC level
+ *
+ * @return int[]
+ */
+ public function getMaxBits():array{
+ $col = array_column(self::MAX_BITS, $this->getOrdinal());
+
+ unset($col[0]); // remove the inavlid index 0
+
+ return $col;
+ }
+
+ /**
+ * Returns the maximum bit length for the given version and current ECC level
+ */
+ public function getMaxBitsForVersion(Version $version):int{
+ return self::MAX_BITS[$version->getVersionNumber()][$this->getOrdinal()];
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Common/GDLuminanceSource.php b/vendor/chillerlan/php-qrcode/src/Common/GDLuminanceSource.php
new file mode 100644
index 000000000..0702a67bb
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Common/GDLuminanceSource.php
@@ -0,0 +1,97 @@
+<?php
+/**
+ * Class GDLuminanceSource
+ *
+ * @created 17.01.2021
+ * @author Ashot Khanamiryan
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license MIT
+ *
+ * @noinspection PhpComposerExtensionStubsInspection
+ */
+
+namespace chillerlan\QRCode\Common;
+
+use chillerlan\QRCode\Decoder\QRCodeDecoderException;
+use chillerlan\Settings\SettingsContainerInterface;
+use function file_get_contents, get_resource_type, imagecolorat, imagecolorsforindex,
+ imagecreatefromstring, imagefilter, imagesx, imagesy, is_resource;
+use const IMG_FILTER_BRIGHTNESS, IMG_FILTER_CONTRAST, IMG_FILTER_GRAYSCALE, IMG_FILTER_NEGATE, PHP_MAJOR_VERSION;
+
+/**
+ * This class is used to help decode images from files which arrive as GD Resource
+ * It does not support rotation.
+ */
+class GDLuminanceSource extends LuminanceSourceAbstract{
+
+ /**
+ * @var resource|\GdImage
+ */
+ protected $gdImage;
+
+ /**
+ * GDLuminanceSource constructor.
+ *
+ * @param resource|\GdImage $gdImage
+ * @param \chillerlan\Settings\SettingsContainerInterface|null $options
+ *
+ * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
+ */
+ public function __construct($gdImage, ?SettingsContainerInterface $options = null){
+
+ /** @noinspection PhpFullyQualifiedNameUsageInspection */
+ if(
+ (PHP_MAJOR_VERSION >= 8 && !$gdImage instanceof \GdImage) // @todo: remove version check in v6
+ || (PHP_MAJOR_VERSION < 8 && (!is_resource($gdImage) || get_resource_type($gdImage) !== 'gd'))
+ ){
+ throw new QRCodeDecoderException('Invalid GD image source.'); // @codeCoverageIgnore
+ }
+
+ parent::__construct(imagesx($gdImage), imagesy($gdImage), $options);
+
+ $this->gdImage = $gdImage;
+
+ if($this->options->readerGrayscale){
+ imagefilter($this->gdImage, IMG_FILTER_GRAYSCALE);
+ }
+
+ if($this->options->readerInvertColors){
+ imagefilter($this->gdImage, IMG_FILTER_NEGATE);
+ }
+
+ if($this->options->readerIncreaseContrast){
+ imagefilter($this->gdImage, IMG_FILTER_BRIGHTNESS, -100);
+ imagefilter($this->gdImage, IMG_FILTER_CONTRAST, -100);
+ }
+
+ $this->setLuminancePixels();
+ }
+
+ /**
+ *
+ */
+ protected function setLuminancePixels():void{
+
+ for($j = 0; $j < $this->height; $j++){
+ for($i = 0; $i < $this->width; $i++){
+ $argb = imagecolorat($this->gdImage, $i, $j);
+ $pixel = imagecolorsforindex($this->gdImage, $argb);
+
+ $this->setLuminancePixel($pixel['red'], $pixel['green'], $pixel['blue']);
+ }
+ }
+
+ }
+
+ /** @inheritDoc */
+ public static function fromFile(string $path, ?SettingsContainerInterface $options = null):self{
+ return new self(imagecreatefromstring(file_get_contents(self::checkFile($path))), $options);
+ }
+
+ /** @inheritDoc */
+ public static function fromBlob(string $blob, ?SettingsContainerInterface $options = null):self{
+ return new self(imagecreatefromstring($blob), $options);
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Common/GF256.php b/vendor/chillerlan/php-qrcode/src/Common/GF256.php
new file mode 100644
index 000000000..d8ba0950b
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Common/GF256.php
@@ -0,0 +1,154 @@
+<?php
+/**
+ * Class GF256
+ *
+ * @created 16.01.2021
+ * @author ZXing Authors
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license Apache-2.0
+ */
+
+namespace chillerlan\QRCode\Common;
+
+use chillerlan\QRCode\QRCodeException;
+
+use function array_fill;
+
+/**
+ * This class contains utility methods for performing mathematical operations over
+ * the Galois Fields. Operations use a given primitive polynomial in calculations.
+ *
+ * Throughout this package, elements of the GF are represented as an int
+ * for convenience and speed (but at the cost of memory).
+ *
+ *
+ * @author Sean Owen
+ * @author David Olivier
+ */
+final class GF256{
+
+ /**
+ * irreducible polynomial whose coefficients are represented by the bits of an int,
+ * where the least-significant bit represents the constant coefficient
+ */
+# private int $primitive = 0x011D;
+
+ private const logTable = [
+ 0, // the first value is never returned, index starts at 1
+ 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75,
+ 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113,
+ 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69,
+ 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166,
+ 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136,
+ 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64,
+ 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61,
+ 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87,
+ 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24,
+ 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46,
+ 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97,
+ 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162,
+ 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246,
+ 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90,
+ 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215,
+ 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175,
+ ];
+
+ private const expTable = [
+ 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38,
+ 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192,
+ 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35,
+ 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161,
+ 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240,
+ 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226,
+ 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206,
+ 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204,
+ 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84,
+ 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115,
+ 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255,
+ 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65,
+ 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166,
+ 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9,
+ 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22,
+ 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1,
+ ];
+
+ /**
+ * Implements both addition and subtraction -- they are the same in GF(size).
+ *
+ * @return int sum/difference of a and b
+ */
+ public static function addOrSubtract(int $a, int $b):int{
+ return ($a ^ $b);
+ }
+
+ /**
+ * @return GenericGFPoly the monomial representing coefficient * x^degree
+ * @throws \chillerlan\QRCode\QRCodeException
+ */
+ public static function buildMonomial(int $degree, int $coefficient):GenericGFPoly{
+
+ if($degree < 0){
+ throw new QRCodeException('degree < 0');
+ }
+
+ $coefficients = array_fill(0, ($degree + 1), 0);
+ $coefficients[0] = $coefficient;
+
+ return new GenericGFPoly($coefficients);
+ }
+
+ /**
+ * @return int 2 to the power of $a in GF(size)
+ */
+ public static function exp(int $a):int{
+
+ if($a < 0){
+ $a += 255;
+ }
+ elseif($a >= 256){
+ $a -= 255;
+ }
+
+ return self::expTable[$a];
+ }
+
+ /**
+ * @return int base 2 log of $a in GF(size)
+ * @throws \chillerlan\QRCode\QRCodeException
+ */
+ public static function log(int $a):int{
+
+ if($a < 1){
+ throw new QRCodeException('$a < 1');
+ }
+
+ return self::logTable[$a];
+ }
+
+ /**
+ * @return int multiplicative inverse of a
+ * @throws \chillerlan\QRCode\QRCodeException
+ */
+ public static function inverse(int $a):int{
+
+ if($a === 0){
+ throw new QRCodeException('$a === 0');
+ }
+
+ return self::expTable[(256 - self::logTable[$a] - 1)];
+ }
+
+ /**
+ * @return int product of a and b in GF(size)
+ */
+ public static function multiply(int $a, int $b):int{
+
+ if($a === 0 || $b === 0){
+ return 0;
+ }
+
+ return self::expTable[((self::logTable[$a] + self::logTable[$b]) % 255)];
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php b/vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php
new file mode 100644
index 000000000..4023e74d2
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php
@@ -0,0 +1,263 @@
+<?php
+/**
+ * Class GenericGFPoly
+ *
+ * @created 16.01.2021
+ * @author ZXing Authors
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license Apache-2.0
+ */
+
+namespace chillerlan\QRCode\Common;
+
+use chillerlan\QRCode\QRCodeException;
+use function array_fill, array_slice, array_splice, count;
+
+/**
+ * Represents a polynomial whose coefficients are elements of a GF.
+ * Instances of this class are immutable.
+ *
+ * Much credit is due to William Rucklidge since portions of this code are an indirect
+ * port of his C++ Reed-Solomon implementation.
+ *
+ * @author Sean Owen
+ */
+final class GenericGFPoly{
+
+ private array $coefficients;
+
+ /**
+ * @param array $coefficients array coefficients as ints representing elements of GF(size), arranged
+ * from most significant (highest-power term) coefficient to the least significant
+ * @param int|null $degree
+ *
+ * @throws \chillerlan\QRCode\QRCodeException if argument is null or empty, or if leading coefficient is 0 and this
+ * is not a constant polynomial (that is, it is not the monomial "0")
+ */
+ public function __construct(array $coefficients, ?int $degree = null){
+ $degree ??= 0;
+
+ if(empty($coefficients)){
+ throw new QRCodeException('arg $coefficients is empty');
+ }
+
+ if($degree < 0){
+ throw new QRCodeException('negative degree');
+ }
+
+ $coefficientsLength = count($coefficients);
+
+ // Leading term must be non-zero for anything except the constant polynomial "0"
+ $firstNonZero = 0;
+
+ while($firstNonZero < $coefficientsLength && $coefficients[$firstNonZero] === 0){
+ $firstNonZero++;
+ }
+
+ $this->coefficients = [0];
+
+ if($firstNonZero !== $coefficientsLength){
+ $this->coefficients = array_fill(0, ($coefficientsLength - $firstNonZero + $degree), 0);
+
+ for($i = 0; $i < ($coefficientsLength - $firstNonZero); $i++){
+ $this->coefficients[$i] = $coefficients[($i + $firstNonZero)];
+ }
+ }
+
+ }
+
+ /**
+ * @return int $coefficient of x^degree term in this polynomial
+ */
+ public function getCoefficient(int $degree):int{
+ return $this->coefficients[(count($this->coefficients) - 1 - $degree)];
+ }
+
+ /**
+ * @return int[]
+ */
+ public function getCoefficients():array{
+ return $this->coefficients;
+ }
+
+ /**
+ * @return int $degree of this polynomial
+ */
+ public function getDegree():int{
+ return (count($this->coefficients) - 1);
+ }
+
+ /**
+ * @return bool true if this polynomial is the monomial "0"
+ */
+ public function isZero():bool{
+ return $this->coefficients[0] === 0;
+ }
+
+ /**
+ * @return int evaluation of this polynomial at a given point
+ */
+ public function evaluateAt(int $a):int{
+
+ if($a === 0){
+ // Just return the x^0 coefficient
+ return $this->getCoefficient(0);
+ }
+
+ $result = 0;
+
+ foreach($this->coefficients as $c){
+ // if $a === 1 just the sum of the coefficients
+ $result = GF256::addOrSubtract((($a === 1) ? $result : GF256::multiply($a, $result)), $c);
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ public function multiply(GenericGFPoly $other):self{
+
+ if($this->isZero() || $other->isZero()){
+ return new self([0]);
+ }
+
+ $product = array_fill(0, (count($this->coefficients) + count($other->coefficients) - 1), 0);
+
+ foreach($this->coefficients as $i => $aCoeff){
+ foreach($other->coefficients as $j => $bCoeff){
+ $product[($i + $j)] ^= GF256::multiply($aCoeff, $bCoeff);
+ }
+ }
+
+ return new self($product);
+ }
+
+ /**
+ * @return \chillerlan\QRCode\Common\GenericGFPoly[] [quotient, remainder]
+ * @throws \chillerlan\QRCode\QRCodeException
+ */
+ public function divide(GenericGFPoly $other):array{
+
+ if($other->isZero()){
+ throw new QRCodeException('Division by 0');
+ }
+
+ $quotient = new self([0]);
+ $remainder = clone $this;
+
+ $denominatorLeadingTerm = $other->getCoefficient($other->getDegree());
+ $inverseDenominatorLeadingTerm = GF256::inverse($denominatorLeadingTerm);
+
+ while($remainder->getDegree() >= $other->getDegree() && !$remainder->isZero()){
+ $scale = GF256::multiply($remainder->getCoefficient($remainder->getDegree()), $inverseDenominatorLeadingTerm);
+ $diff = ($remainder->getDegree() - $other->getDegree());
+ $quotient = $quotient->addOrSubtract(GF256::buildMonomial($diff, $scale));
+ $remainder = $remainder->addOrSubtract($other->multiplyByMonomial($diff, $scale));
+ }
+
+ return [$quotient, $remainder];
+
+ }
+
+ /**
+ *
+ */
+ public function multiplyInt(int $scalar):self{
+
+ if($scalar === 0){
+ return new self([0]);
+ }
+
+ if($scalar === 1){
+ return $this;
+ }
+
+ $product = array_fill(0, count($this->coefficients), 0);
+
+ foreach($this->coefficients as $i => $c){
+ $product[$i] = GF256::multiply($c, $scalar);
+ }
+
+ return new self($product);
+ }
+
+ /**
+ * @throws \chillerlan\QRCode\QRCodeException
+ */
+ public function multiplyByMonomial(int $degree, int $coefficient):self{
+
+ if($degree < 0){
+ throw new QRCodeException('degree < 0');
+ }
+
+ if($coefficient === 0){
+ return new self([0]);
+ }
+
+ $product = array_fill(0, (count($this->coefficients) + $degree), 0);
+
+ foreach($this->coefficients as $i => $c){
+ $product[$i] = GF256::multiply($c, $coefficient);
+ }
+
+ return new self($product);
+ }
+
+ /**
+ *
+ */
+ public function mod(GenericGFPoly $other):self{
+
+ if((count($this->coefficients) - count($other->coefficients)) < 0){
+ return $this;
+ }
+
+ $ratio = (GF256::log($this->coefficients[0]) - GF256::log($other->coefficients[0]));
+
+ foreach($other->coefficients as $i => $c){
+ $this->coefficients[$i] ^= GF256::exp(GF256::log($c) + $ratio);
+ }
+
+ return (new self($this->coefficients))->mod($other);
+ }
+
+ /**
+ *
+ */
+ public function addOrSubtract(GenericGFPoly $other):self{
+
+ if($this->isZero()){
+ return $other;
+ }
+
+ if($other->isZero()){
+ return $this;
+ }
+
+ $smallerCoefficients = $this->coefficients;
+ $largerCoefficients = $other->coefficients;
+
+ if(count($smallerCoefficients) > count($largerCoefficients)){
+ $temp = $smallerCoefficients;
+ $smallerCoefficients = $largerCoefficients;
+ $largerCoefficients = $temp;
+ }
+
+ $sumDiff = array_fill(0, count($largerCoefficients), 0);
+ $lengthDiff = (count($largerCoefficients) - count($smallerCoefficients));
+ // Copy high-order terms only found in higher-degree polynomial's coefficients
+ array_splice($sumDiff, 0, $lengthDiff, array_slice($largerCoefficients, 0, $lengthDiff));
+
+ $countLargerCoefficients = count($largerCoefficients);
+
+ for($i = $lengthDiff; $i < $countLargerCoefficients; $i++){
+ $sumDiff[$i] = GF256::addOrSubtract($smallerCoefficients[($i - $lengthDiff)], $largerCoefficients[$i]);
+ }
+
+ return new self($sumDiff);
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Common/IMagickLuminanceSource.php b/vendor/chillerlan/php-qrcode/src/Common/IMagickLuminanceSource.php
new file mode 100644
index 000000000..ade994a78
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Common/IMagickLuminanceSource.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Class IMagickLuminanceSource
+ *
+ * @created 17.01.2021
+ * @author Ashot Khanamiryan
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license MIT
+ *
+ * @noinspection PhpComposerExtensionStubsInspection
+ */
+
+namespace chillerlan\QRCode\Common;
+
+use chillerlan\Settings\SettingsContainerInterface;
+use Imagick;
+use function count;
+
+/**
+ * This class is used to help decode images from files which arrive as Imagick Resource
+ * It does not support rotation.
+ */
+class IMagickLuminanceSource extends LuminanceSourceAbstract{
+
+ protected Imagick $imagick;
+
+ /**
+ * IMagickLuminanceSource constructor.
+ */
+ public function __construct(Imagick $imagick, ?SettingsContainerInterface $options = null){
+ parent::__construct($imagick->getImageWidth(), $imagick->getImageHeight(), $options);
+
+ $this->imagick = $imagick;
+
+ if($this->options->readerGrayscale){
+ $this->imagick->setImageColorspace(Imagick::COLORSPACE_GRAY);
+ }
+
+ if($this->options->readerInvertColors){
+ $this->imagick->negateImage($this->options->readerGrayscale);
+ }
+
+ if($this->options->readerIncreaseContrast){
+ for($i = 0; $i < 10; $i++){
+ $this->imagick->contrastImage(false); // misleading docs
+ }
+ }
+
+ $this->setLuminancePixels();
+ }
+
+ /**
+ *
+ */
+ protected function setLuminancePixels():void{
+ $pixels = $this->imagick->exportImagePixels(1, 1, $this->width, $this->height, 'RGB', Imagick::PIXEL_CHAR);
+ $count = count($pixels);
+
+ for($i = 0; $i < $count; $i += 3){
+ $this->setLuminancePixel(($pixels[$i] & 0xff), ($pixels[($i + 1)] & 0xff), ($pixels[($i + 2)] & 0xff));
+ }
+ }
+
+ /** @inheritDoc */
+ public static function fromFile(string $path, ?SettingsContainerInterface $options = null):self{
+ return new self(new Imagick(self::checkFile($path)), $options);
+ }
+
+ /** @inheritDoc */
+ public static function fromBlob(string $blob, ?SettingsContainerInterface $options = null):self{
+ $im = new Imagick;
+ $im->readImageBlob($blob);
+
+ return new self($im, $options);
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceAbstract.php b/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceAbstract.php
new file mode 100644
index 000000000..e4373b87d
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceAbstract.php
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Class LuminanceSourceAbstract
+ *
+ * @created 24.01.2021
+ * @author ZXing Authors
+ * @author Ashot Khanamiryan
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license Apache-2.0
+ */
+
+namespace chillerlan\QRCode\Common;
+
+use chillerlan\QRCode\Decoder\QRCodeDecoderException;
+use chillerlan\QRCode\QROptions;
+use chillerlan\Settings\SettingsContainerInterface;
+use function array_slice, array_splice, file_exists, is_file, is_readable, realpath;
+
+/**
+ * The purpose of this class hierarchy is to abstract different bitmap implementations across
+ * platforms into a standard interface for requesting greyscale luminance values.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+abstract class LuminanceSourceAbstract implements LuminanceSourceInterface{
+
+ /** @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface */
+ protected SettingsContainerInterface $options;
+ protected array $luminances;
+ protected int $width;
+ protected int $height;
+
+ /**
+ *
+ */
+ public function __construct(int $width, int $height, ?SettingsContainerInterface $options = null){
+ $this->width = $width;
+ $this->height = $height;
+ $this->options = ($options ?? new QROptions);
+
+ $this->luminances = [];
+ }
+
+ /** @inheritDoc */
+ public function getLuminances():array{
+ return $this->luminances;
+ }
+
+ /** @inheritDoc */
+ public function getWidth():int{
+ return $this->width;
+ }
+
+ /** @inheritDoc */
+ public function getHeight():int{
+ return $this->height;
+ }
+
+ /**
+ * @inheritDoc
+ * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
+ */
+ public function getRow(int $y):array{
+
+ if($y < 0 || $y >= $this->getHeight()){
+ throw new QRCodeDecoderException('Requested row is outside the image: '.$y);
+ }
+
+ $arr = [];
+
+ array_splice($arr, 0, $this->width, array_slice($this->luminances, ($y * $this->width), $this->width));
+
+ return $arr;
+ }
+
+ /**
+ *
+ */
+ protected function setLuminancePixel(int $r, int $g, int $b):void{
+ $this->luminances[] = ($r === $g && $g === $b)
+ // Image is already greyscale, so pick any channel.
+ ? $r // (($r + 128) % 256) - 128;
+ // Calculate luminance cheaply, favoring green.
+ : (($r + 2 * $g + $b) / 4); // (((($r + 2 * $g + $b) / 4) + 128) % 256) - 128;
+ }
+
+ /**
+ * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
+ */
+ protected static function checkFile(string $path):string{
+ $path = trim($path);
+
+ if(!file_exists($path) || !is_file($path) || !is_readable($path)){
+ throw new QRCodeDecoderException('invalid file: '.$path);
+ }
+
+ $realpath = realpath($path);
+
+ if($realpath === false){
+ throw new QRCodeDecoderException('unable to resolve path: '.$path);
+ }
+
+ return $realpath;
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceInterface.php b/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceInterface.php
new file mode 100644
index 000000000..64409e36a
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Common/LuminanceSourceInterface.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Interface LuminanceSourceInterface
+ *
+ * @created 18.11.2021
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2021 smiley
+ * @license MIT
+ */
+
+namespace chillerlan\QRCode\Common;
+
+/**
+ */
+interface LuminanceSourceInterface{
+
+ /**
+ * Fetches luminance data for the underlying bitmap. Values should be fetched using:
+ * `int luminance = array[y * width + x] & 0xff`
+ *
+ * @return array A row-major 2D array of luminance values. Do not use result $length as it may be
+ * larger than $width * $height bytes on some platforms. Do not modify the contents
+ * of the result.
+ */
+ public function getLuminances():array;
+
+ /**
+ * @return int The width of the bitmap.
+ */
+ public function getWidth():int;
+
+ /**
+ * @return int The height of the bitmap.
+ */
+ public function getHeight():int;
+
+ /**
+ * Fetches one row of luminance data from the underlying platform's bitmap. Values range from
+ * 0 (black) to 255 (white). Because Java does not have an unsigned byte type, callers will have
+ * to bitwise and with 0xff for each value. It is preferable for implementations of this method
+ * to only fetch this row rather than the whole image, since no 2D Readers may be installed and
+ * getLuminances() may never be called.
+ *
+ * @param int $y The row to fetch, which must be in [0,getHeight())
+ *
+ * @return array An array containing the luminance data.
+ * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
+ */
+ public function getRow(int $y):array;
+
+ /**
+ * Creates a LuminanceSource instance from the given file
+ */
+ public static function fromFile(string $path):self;
+
+ /**
+ * Creates a LuminanceSource instance from the given data blob
+ */
+ public static function fromBlob(string $blob):self;
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Common/MaskPattern.php b/vendor/chillerlan/php-qrcode/src/Common/MaskPattern.php
new file mode 100644
index 000000000..5c3ea93c1
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Common/MaskPattern.php
@@ -0,0 +1,329 @@
+<?php
+/**
+ * Class MaskPattern
+ *
+ * @created 19.01.2021
+ * @author ZXing Authors
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license Apache-2.0
+ */
+
+namespace chillerlan\QRCode\Common;
+
+use chillerlan\QRCode\QRCodeException;
+use chillerlan\QRCode\Data\QRMatrix;
+use Closure;
+use function abs, array_column, array_search, intdiv, min;
+
+/**
+ * ISO/IEC 18004:2000 Section 8.8.1
+ * ISO/IEC 18004:2000 Section 8.8.2 - Evaluation of masking results
+ *
+ * @see http://www.thonky.com/qr-code-tutorial/data-masking
+ * @see https://github.com/zxing/zxing/blob/e9e2bd280bcaeabd59d0f955798384fe6c018a6c/core/src/main/java/com/google/zxing/qrcode/encoder/MaskUtil.java
+ */
+final class MaskPattern{
+
+ /**
+ * @see \chillerlan\QRCode\QROptionsTrait::$maskPattern
+ *
+ * @var int
+ */
+ public const AUTO = -1;
+
+ public const PATTERN_000 = 0b000;
+ public const PATTERN_001 = 0b001;
+ public const PATTERN_010 = 0b010;
+ public const PATTERN_011 = 0b011;
+ public const PATTERN_100 = 0b100;
+ public const PATTERN_101 = 0b101;
+ public const PATTERN_110 = 0b110;
+ public const PATTERN_111 = 0b111;
+
+ /**
+ * @var int[]
+ */
+ public const PATTERNS = [
+ self::PATTERN_000,
+ self::PATTERN_001,
+ self::PATTERN_010,
+ self::PATTERN_011,
+ self::PATTERN_100,
+ self::PATTERN_101,
+ self::PATTERN_110,
+ self::PATTERN_111,
+ ];
+
+ /*
+ * Penalty scores
+ *
+ * ISO/IEC 18004:2000 Section 8.8.1 - Table 24
+ */
+ private const PENALTY_N1 = 3;
+ private const PENALTY_N2 = 3;
+ private const PENALTY_N3 = 40;
+ private const PENALTY_N4 = 10;
+
+ /**
+ * The current mask pattern value (0-7)
+ */
+ private int $maskPattern;
+
+ /**
+ * MaskPattern constructor.
+ *
+ * @throws \chillerlan\QRCode\QRCodeException
+ */
+ public function __construct(int $maskPattern){
+
+ if(($maskPattern & 0b111) !== $maskPattern){
+ throw new QRCodeException('invalid mask pattern');
+ }
+
+ $this->maskPattern = $maskPattern;
+ }
+
+ /**
+ * Returns the current mask pattern
+ */
+ public function getPattern():int{
+ return $this->maskPattern;
+ }
+
+ /**
+ * Returns a closure that applies the mask for the chosen mask pattern.
+ *
+ * Note that the diagram in section 6.8.1 is misleading since it indicates that $i is column position
+ * and $j is row position. In fact, as the text says, $i is row position and $j is column position.
+ *
+ * @see https://www.thonky.com/qr-code-tutorial/mask-patterns
+ * @see https://github.com/zxing/zxing/blob/e9e2bd280bcaeabd59d0f955798384fe6c018a6c/core/src/main/java/com/google/zxing/qrcode/decoder/DataMask.java#L32-L117
+ */
+ public function getMask():Closure{
+ // $x = column (width), $y = row (height)
+ return [
+ self::PATTERN_000 => fn(int $x, int $y):bool => (($x + $y) % 2) === 0,
+ self::PATTERN_001 => fn(int $x, int $y):bool => ($y % 2) === 0,
+ self::PATTERN_010 => fn(int $x, int $y):bool => ($x % 3) === 0,
+ self::PATTERN_011 => fn(int $x, int $y):bool => (($x + $y) % 3) === 0,
+ self::PATTERN_100 => fn(int $x, int $y):bool => ((intdiv($y, 2) + intdiv($x, 3)) % 2) === 0,
+ self::PATTERN_101 => fn(int $x, int $y):bool => (($x * $y) % 6) === 0,
+ self::PATTERN_110 => fn(int $x, int $y):bool => (($x * $y) % 6) < 3,
+ self::PATTERN_111 => fn(int $x, int $y):bool => (($x + $y + (($x * $y) % 3)) % 2) === 0,
+ ][$this->maskPattern];
+ }
+
+ /**
+ * Evaluates the matrix of the given data interface and returns a new mask pattern instance for the best result
+ */
+ public static function getBestPattern(QRMatrix $QRMatrix):self{
+ $penalties = [];
+ $size = $QRMatrix->getSize();
+
+ foreach(self::PATTERNS as $pattern){
+ $mp = new self($pattern);
+ $matrix = (clone $QRMatrix)->setFormatInfo($mp)->mask($mp)->getMatrix(true);
+ $penalty = 0;
+
+ for($level = 1; $level <= 4; $level++){
+ $penalty += self::{'testRule'.$level}($matrix, $size, $size);
+ }
+
+ $penalties[$pattern] = (int)$penalty;
+ }
+
+ return new self(array_search(min($penalties), $penalties, true));
+ }
+
+ /**
+ * Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and
+ * give penalty to them. Example: 00000 or 11111.
+ */
+ public static function testRule1(array $matrix, int $height, int $width):int{
+ $penalty = 0;
+
+ // horizontal
+ foreach($matrix as $row){
+ $penalty += self::applyRule1($row);
+ }
+
+ // vertical
+ for($x = 0; $x < $width; $x++){
+ $penalty += self::applyRule1(array_column($matrix, $x));
+ }
+
+ return $penalty;
+ }
+
+ /**
+ *
+ */
+ private static function applyRule1(array $rc):int{
+ $penalty = 0;
+ $numSameBitCells = 0;
+ $prevBit = null;
+
+ foreach($rc as $val){
+
+ if($val === $prevBit){
+ $numSameBitCells++;
+ }
+ else{
+
+ if($numSameBitCells >= 5){
+ $penalty += (self::PENALTY_N1 + $numSameBitCells - 5);
+ }
+
+ $numSameBitCells = 1; // Include the cell itself.
+ $prevBit = $val;
+ }
+ }
+
+ if($numSameBitCells >= 5){
+ $penalty += (self::PENALTY_N1 + $numSameBitCells - 5);
+ }
+
+ return $penalty;
+ }
+
+ /**
+ * Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give
+ * penalty to them. This is actually equivalent to the spec's rule, which is to find MxN blocks and give a
+ * penalty proportional to (M-1)x(N-1), because this is the number of 2x2 blocks inside such a block.
+ */
+ public static function testRule2(array $matrix, int $height, int $width):int{
+ $penalty = 0;
+
+ foreach($matrix as $y => $row){
+
+ if($y > ($height - 2)){
+ break;
+ }
+
+ foreach($row as $x => $val){
+
+ if($x > ($width - 2)){
+ break;
+ }
+
+ if(
+ $val === $row[($x + 1)]
+ && $val === $matrix[($y + 1)][$x]
+ && $val === $matrix[($y + 1)][($x + 1)]
+ ){
+ $penalty++;
+ }
+ }
+ }
+
+ return (self::PENALTY_N2 * $penalty);
+ }
+
+ /**
+ * Apply mask penalty rule 3 and return the penalty. Find consecutive runs of 1:1:3:1:1:4
+ * starting with black, or 4:1:1:3:1:1 starting with white, and give penalty to them. If we
+ * find patterns like 000010111010000, we give penalty once.
+ */
+ public static function testRule3(array $matrix, int $height, int $width):int{
+ $penalties = 0;
+
+ foreach($matrix as $y => $row){
+ foreach($row as $x => $val){
+
+ if(
+ ($x + 6) < $width
+ && $val
+ && !$row[($x + 1)]
+ && $row[($x + 2)]
+ && $row[($x + 3)]
+ && $row[($x + 4)]
+ && !$row[($x + 5)]
+ && $row[($x + 6)]
+ && (
+ self::isWhiteHorizontal($row, $width, ($x - 4), $x)
+ || self::isWhiteHorizontal($row, $width, ($x + 7), ($x + 11))
+ )
+ ){
+ $penalties++;
+ }
+
+ if(
+ ($y + 6) < $height
+ && $val
+ && !$matrix[($y + 1)][$x]
+ && $matrix[($y + 2)][$x]
+ && $matrix[($y + 3)][$x]
+ && $matrix[($y + 4)][$x]
+ && !$matrix[($y + 5)][$x]
+ && $matrix[($y + 6)][$x]
+ && (
+ self::isWhiteVertical($matrix, $height, $x, ($y - 4), $y)
+ || self::isWhiteVertical($matrix, $height, $x, ($y + 7), ($y + 11))
+ )
+ ){
+ $penalties++;
+ }
+
+ }
+ }
+
+ return ($penalties * self::PENALTY_N3);
+ }
+
+ /**
+ *
+ */
+ private static function isWhiteHorizontal(array $row, int $width, int $from, int $to):bool{
+
+ if($from < 0 || $width < $to){
+ return false;
+ }
+
+ for($x = $from; $x < $to; $x++){
+ if($row[$x]){
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ *
+ */
+ private static function isWhiteVertical(array $matrix, int $height, int $x, int $from, int $to):bool{
+
+ if($from < 0 || $height < $to){
+ return false;
+ }
+
+ for($y = $from; $y < $to; $y++){
+ if($matrix[$y][$x] === true){
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give
+ * penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance.
+ */
+ public static function testRule4(array $matrix, int $height, int $width):int{
+ $darkCells = 0;
+ $totalCells = ($height * $width);
+
+ foreach($matrix as $row){
+ foreach($row as $val){
+ if($val === true){
+ $darkCells++;
+ }
+ }
+ }
+
+ return (intdiv((abs($darkCells * 2 - $totalCells) * 10), $totalCells) * self::PENALTY_N4);
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Common/Mode.php b/vendor/chillerlan/php-qrcode/src/Common/Mode.php
new file mode 100644
index 000000000..523d37919
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Common/Mode.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * Class Mode
+ *
+ * @created 19.11.2020
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2020 smiley
+ * @license MIT
+ */
+
+namespace chillerlan\QRCode\Common;
+
+use chillerlan\QRCode\Data\{AlphaNum, Byte, Hanzi, Kanji, Number};
+use chillerlan\QRCode\QRCodeException;
+
+/**
+ * Data mode information - ISO 18004:2006, 6.4.1, Tables 2 and 3
+ */
+final class Mode{
+
+ // ISO/IEC 18004:2000 Table 2
+
+ /** @var int */
+ public const TERMINATOR = 0b0000;
+ /** @var int */
+ public const NUMBER = 0b0001;
+ /** @var int */
+ public const ALPHANUM = 0b0010;
+ /** @var int */
+ public const BYTE = 0b0100;
+ /** @var int */
+ public const KANJI = 0b1000;
+ /** @var int */
+ public const HANZI = 0b1101;
+ /** @var int */
+ public const STRCTURED_APPEND = 0b0011;
+ /** @var int */
+ public const FNC1_FIRST = 0b0101;
+ /** @var int */
+ public const FNC1_SECOND = 0b1001;
+ /** @var int */
+ public const ECI = 0b0111;
+
+ /**
+ * 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
+ */
+ public const LENGTH_BITS = [
+ self::NUMBER => [10, 12, 14],
+ self::ALPHANUM => [ 9, 11, 13],
+ self::BYTE => [ 8, 16, 16],
+ self::KANJI => [ 8, 10, 12],
+ self::HANZI => [ 8, 10, 12],
+ self::ECI => [ 0, 0, 0],
+ ];
+
+ /**
+ * Map of data mode => interface (detection order)
+ *
+ * @var string[]
+ */
+ public const INTERFACES = [
+ self::NUMBER => Number::class,
+ self::ALPHANUM => AlphaNum::class,
+ self::KANJI => Kanji::class,
+ self::HANZI => Hanzi::class,
+ self::BYTE => Byte::class,
+ ];
+
+ /**
+ * returns the length bits for the version breakpoints 1-9, 10-26 and 27-40
+ *
+ * @throws \chillerlan\QRCode\QRCodeException
+ */
+ public static function getLengthBitsForVersion(int $mode, int $version):int{
+
+ if(!isset(self::LENGTH_BITS[$mode])){
+ throw new QRCodeException('invalid mode given');
+ }
+
+ $minVersion = 0;
+
+ foreach([9, 26, 40] as $key => $breakpoint){
+
+ if($version > $minVersion && $version <= $breakpoint){
+ return self::LENGTH_BITS[$mode][$key];
+ }
+
+ $minVersion = $breakpoint;
+ }
+
+ throw new QRCodeException(sprintf('invalid version number: %d', $version));
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Common/Version.php b/vendor/chillerlan/php-qrcode/src/Common/Version.php
new file mode 100644
index 000000000..fe7240f8a
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Common/Version.php
@@ -0,0 +1,287 @@
+<?php
+/**
+ * Class Version
+ *
+ * @created 19.11.2020
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2020 smiley
+ * @license MIT
+ */
+
+namespace chillerlan\QRCode\Common;
+
+use chillerlan\QRCode\QRCodeException;
+
+/**
+ * Version related tables and methods
+ */
+final class Version{
+
+ /**
+ * Enable version auto detection
+ *
+ * @see \chillerlan\QRCode\QROptionsTrait::$version
+ *
+ * @var int
+ */
+ public const AUTO = -1;
+
+ /**
+ * ISO/IEC 18004:2000 Annex E, Table E.1 - Row/column coordinates of center module of Alignment Patterns
+ *
+ * version -> pattern
+ *
+ * @var int[][]
+ */
+ private const ALIGNMENT_PATTERN = [
+ 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[]
+ */
+ private const VERSION_PATTERN = [
+ 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,
+ ];
+
+ /**
+ * ISO/IEC 18004:2000 Tables 13-22 - Error correction characteristics
+ *
+ * @see http://www.thonky.com/qr-code-tutorial/error-correction-table
+ */
+ private const RSBLOCKS = [
+ 1 => [[ 7, [[ 1, 19], [ 0, 0]]], [10, [[ 1, 16], [ 0, 0]]], [13, [[ 1, 13], [ 0, 0]]], [17, [[ 1, 9], [ 0, 0]]]],
+ 2 => [[10, [[ 1, 34], [ 0, 0]]], [16, [[ 1, 28], [ 0, 0]]], [22, [[ 1, 22], [ 0, 0]]], [28, [[ 1, 16], [ 0, 0]]]],
+ 3 => [[15, [[ 1, 55], [ 0, 0]]], [26, [[ 1, 44], [ 0, 0]]], [18, [[ 2, 17], [ 0, 0]]], [22, [[ 2, 13], [ 0, 0]]]],
+ 4 => [[20, [[ 1, 80], [ 0, 0]]], [18, [[ 2, 32], [ 0, 0]]], [26, [[ 2, 24], [ 0, 0]]], [16, [[ 4, 9], [ 0, 0]]]],
+ 5 => [[26, [[ 1, 108], [ 0, 0]]], [24, [[ 2, 43], [ 0, 0]]], [18, [[ 2, 15], [ 2, 16]]], [22, [[ 2, 11], [ 2, 12]]]],
+ 6 => [[18, [[ 2, 68], [ 0, 0]]], [16, [[ 4, 27], [ 0, 0]]], [24, [[ 4, 19], [ 0, 0]]], [28, [[ 4, 15], [ 0, 0]]]],
+ 7 => [[20, [[ 2, 78], [ 0, 0]]], [18, [[ 4, 31], [ 0, 0]]], [18, [[ 2, 14], [ 4, 15]]], [26, [[ 4, 13], [ 1, 14]]]],
+ 8 => [[24, [[ 2, 97], [ 0, 0]]], [22, [[ 2, 38], [ 2, 39]]], [22, [[ 4, 18], [ 2, 19]]], [26, [[ 4, 14], [ 2, 15]]]],
+ 9 => [[30, [[ 2, 116], [ 0, 0]]], [22, [[ 3, 36], [ 2, 37]]], [20, [[ 4, 16], [ 4, 17]]], [24, [[ 4, 12], [ 4, 13]]]],
+ 10 => [[18, [[ 2, 68], [ 2, 69]]], [26, [[ 4, 43], [ 1, 44]]], [24, [[ 6, 19], [ 2, 20]]], [28, [[ 6, 15], [ 2, 16]]]],
+ 11 => [[20, [[ 4, 81], [ 0, 0]]], [30, [[ 1, 50], [ 4, 51]]], [28, [[ 4, 22], [ 4, 23]]], [24, [[ 3, 12], [ 8, 13]]]],
+ 12 => [[24, [[ 2, 92], [ 2, 93]]], [22, [[ 6, 36], [ 2, 37]]], [26, [[ 4, 20], [ 6, 21]]], [28, [[ 7, 14], [ 4, 15]]]],
+ 13 => [[26, [[ 4, 107], [ 0, 0]]], [22, [[ 8, 37], [ 1, 38]]], [24, [[ 8, 20], [ 4, 21]]], [22, [[12, 11], [ 4, 12]]]],
+ 14 => [[30, [[ 3, 115], [ 1, 116]]], [24, [[ 4, 40], [ 5, 41]]], [20, [[11, 16], [ 5, 17]]], [24, [[11, 12], [ 5, 13]]]],
+ 15 => [[22, [[ 5, 87], [ 1, 88]]], [24, [[ 5, 41], [ 5, 42]]], [30, [[ 5, 24], [ 7, 25]]], [24, [[11, 12], [ 7, 13]]]],
+ 16 => [[24, [[ 5, 98], [ 1, 99]]], [28, [[ 7, 45], [ 3, 46]]], [24, [[15, 19], [ 2, 20]]], [30, [[ 3, 15], [13, 16]]]],
+ 17 => [[28, [[ 1, 107], [ 5, 108]]], [28, [[10, 46], [ 1, 47]]], [28, [[ 1, 22], [15, 23]]], [28, [[ 2, 14], [17, 15]]]],
+ 18 => [[30, [[ 5, 120], [ 1, 121]]], [26, [[ 9, 43], [ 4, 44]]], [28, [[17, 22], [ 1, 23]]], [28, [[ 2, 14], [19, 15]]]],
+ 19 => [[28, [[ 3, 113], [ 4, 114]]], [26, [[ 3, 44], [11, 45]]], [26, [[17, 21], [ 4, 22]]], [26, [[ 9, 13], [16, 14]]]],
+ 20 => [[28, [[ 3, 107], [ 5, 108]]], [26, [[ 3, 41], [13, 42]]], [30, [[15, 24], [ 5, 25]]], [28, [[15, 15], [10, 16]]]],
+ 21 => [[28, [[ 4, 116], [ 4, 117]]], [26, [[17, 42], [ 0, 0]]], [28, [[17, 22], [ 6, 23]]], [30, [[19, 16], [ 6, 17]]]],
+ 22 => [[28, [[ 2, 111], [ 7, 112]]], [28, [[17, 46], [ 0, 0]]], [30, [[ 7, 24], [16, 25]]], [24, [[34, 13], [ 0, 0]]]],
+ 23 => [[30, [[ 4, 121], [ 5, 122]]], [28, [[ 4, 47], [14, 48]]], [30, [[11, 24], [14, 25]]], [30, [[16, 15], [14, 16]]]],
+ 24 => [[30, [[ 6, 117], [ 4, 118]]], [28, [[ 6, 45], [14, 46]]], [30, [[11, 24], [16, 25]]], [30, [[30, 16], [ 2, 17]]]],
+ 25 => [[26, [[ 8, 106], [ 4, 107]]], [28, [[ 8, 47], [13, 48]]], [30, [[ 7, 24], [22, 25]]], [30, [[22, 15], [13, 16]]]],
+ 26 => [[28, [[10, 114], [ 2, 115]]], [28, [[19, 46], [ 4, 47]]], [28, [[28, 22], [ 6, 23]]], [30, [[33, 16], [ 4, 17]]]],
+ 27 => [[30, [[ 8, 122], [ 4, 123]]], [28, [[22, 45], [ 3, 46]]], [30, [[ 8, 23], [26, 24]]], [30, [[12, 15], [28, 16]]]],
+ 28 => [[30, [[ 3, 117], [10, 118]]], [28, [[ 3, 45], [23, 46]]], [30, [[ 4, 24], [31, 25]]], [30, [[11, 15], [31, 16]]]],
+ 29 => [[30, [[ 7, 116], [ 7, 117]]], [28, [[21, 45], [ 7, 46]]], [30, [[ 1, 23], [37, 24]]], [30, [[19, 15], [26, 16]]]],
+ 30 => [[30, [[ 5, 115], [10, 116]]], [28, [[19, 47], [10, 48]]], [30, [[15, 24], [25, 25]]], [30, [[23, 15], [25, 16]]]],
+ 31 => [[30, [[13, 115], [ 3, 116]]], [28, [[ 2, 46], [29, 47]]], [30, [[42, 24], [ 1, 25]]], [30, [[23, 15], [28, 16]]]],
+ 32 => [[30, [[17, 115], [ 0, 0]]], [28, [[10, 46], [23, 47]]], [30, [[10, 24], [35, 25]]], [30, [[19, 15], [35, 16]]]],
+ 33 => [[30, [[17, 115], [ 1, 116]]], [28, [[14, 46], [21, 47]]], [30, [[29, 24], [19, 25]]], [30, [[11, 15], [46, 16]]]],
+ 34 => [[30, [[13, 115], [ 6, 116]]], [28, [[14, 46], [23, 47]]], [30, [[44, 24], [ 7, 25]]], [30, [[59, 16], [ 1, 17]]]],
+ 35 => [[30, [[12, 121], [ 7, 122]]], [28, [[12, 47], [26, 48]]], [30, [[39, 24], [14, 25]]], [30, [[22, 15], [41, 16]]]],
+ 36 => [[30, [[ 6, 121], [14, 122]]], [28, [[ 6, 47], [34, 48]]], [30, [[46, 24], [10, 25]]], [30, [[ 2, 15], [64, 16]]]],
+ 37 => [[30, [[17, 122], [ 4, 123]]], [28, [[29, 46], [14, 47]]], [30, [[49, 24], [10, 25]]], [30, [[24, 15], [46, 16]]]],
+ 38 => [[30, [[ 4, 122], [18, 123]]], [28, [[13, 46], [32, 47]]], [30, [[48, 24], [14, 25]]], [30, [[42, 15], [32, 16]]]],
+ 39 => [[30, [[20, 117], [ 4, 118]]], [28, [[40, 47], [ 7, 48]]], [30, [[43, 24], [22, 25]]], [30, [[10, 15], [67, 16]]]],
+ 40 => [[30, [[19, 118], [ 6, 119]]], [28, [[18, 47], [31, 48]]], [30, [[34, 24], [34, 25]]], [30, [[20, 15], [61, 16]]]],
+ ];
+
+ /**
+ * ISO/IEC 18004:2000 Table 1 - Data capacity of all versions of QR Code
+ */
+ private const TOTAL_CODEWORDS = [
+ 1 => 26,
+ 2 => 44,
+ 3 => 70,
+ 4 => 100,
+ 5 => 134,
+ 6 => 172,
+ 7 => 196,
+ 8 => 242,
+ 9 => 292,
+ 10 => 346,
+ 11 => 404,
+ 12 => 466,
+ 13 => 532,
+ 14 => 581,
+ 15 => 655,
+ 16 => 733,
+ 17 => 815,
+ 18 => 901,
+ 19 => 991,
+ 20 => 1085,
+ 21 => 1156,
+ 22 => 1258,
+ 23 => 1364,
+ 24 => 1474,
+ 25 => 1588,
+ 26 => 1706,
+ 27 => 1828,
+ 28 => 1921,
+ 29 => 2051,
+ 30 => 2185,
+ 31 => 2323,
+ 32 => 2465,
+ 33 => 2611,
+ 34 => 2761,
+ 35 => 2876,
+ 36 => 3034,
+ 37 => 3196,
+ 38 => 3362,
+ 39 => 3532,
+ 40 => 3706,
+ ];
+
+ /**
+ * QR Code version number
+ */
+ private int $version;
+
+ /**
+ * Version constructor.
+ *
+ * @throws \chillerlan\QRCode\QRCodeException
+ */
+ public function __construct(int $version){
+
+ if($version < 1 || $version > 40){
+ throw new QRCodeException('invalid version given');
+ }
+
+ $this->version = $version;
+ }
+
+ /**
+ * returns the current version number as string
+ */
+ public function __toString():string{
+ return (string)$this->version;
+ }
+
+ /**
+ * returns the current version number
+ */
+ public function getVersionNumber():int{
+ return $this->version;
+ }
+
+ /**
+ * the matrix size for the given version
+ */
+ public function getDimension():int{
+ return (($this->version * 4) + 17);
+ }
+
+ /**
+ * the version pattern for the given version
+ */
+ public function getVersionPattern():?int{
+ return (self::VERSION_PATTERN[$this->version] ?? null);
+ }
+
+ /**
+ * the alignment patterns for the current version
+ *
+ * @return int[]
+ */
+ public function getAlignmentPattern():array{
+ return self::ALIGNMENT_PATTERN[$this->version];
+ }
+
+ /**
+ * returns ECC block information for the given $version and $eccLevel
+ */
+ public function getRSBlocks(EccLevel $eccLevel):array{
+ return self::RSBLOCKS[$this->version][$eccLevel->getOrdinal()];
+ }
+
+ /**
+ * returns the maximum codewords for the current version
+ */
+ public function getTotalCodewords():int{
+ return self::TOTAL_CODEWORDS[$this->version];
+ }
+
+}
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];
+ }
+ }
+ }
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Decoder/Binarizer.php b/vendor/chillerlan/php-qrcode/src/Decoder/Binarizer.php
new file mode 100644
index 000000000..7b7b49f65
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Decoder/Binarizer.php
@@ -0,0 +1,361 @@
+<?php
+/**
+ * Class Binarizer
+ *
+ * @created 17.01.2021
+ * @author ZXing Authors
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license Apache-2.0
+ */
+
+namespace chillerlan\QRCode\Decoder;
+
+use chillerlan\QRCode\Common\LuminanceSourceInterface;
+use chillerlan\QRCode\Data\QRMatrix;
+use function array_fill, count, intdiv, max;
+
+/**
+ * This class implements a local thresholding algorithm, which while slower than the
+ * GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for
+ * high frequency images of barcodes with black data on white backgrounds. For this application,
+ * it does a much better job than a global blackpoint with severe shadows and gradients.
+ * However, it tends to produce artifacts on lower frequency images and is therefore not
+ * a good general purpose binarizer for uses outside ZXing.
+ *
+ * This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers,
+ * and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already
+ * inherently local, and only fails for horizontal gradients. We can revisit that problem later,
+ * but for now it was not a win to use local blocks for 1D.
+ *
+ * This Binarizer is the default for the unit tests and the recommended class for library users.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+final class Binarizer{
+
+ // This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels.
+ // So this is the smallest dimension in each axis we can accept.
+ private const BLOCK_SIZE_POWER = 3;
+ private const BLOCK_SIZE = 8; // ...0100...00
+ private const BLOCK_SIZE_MASK = 7; // ...0011...11
+ private const MINIMUM_DIMENSION = 40;
+ private const MIN_DYNAMIC_RANGE = 24;
+
+# private const LUMINANCE_BITS = 5;
+ private const LUMINANCE_SHIFT = 3;
+ private const LUMINANCE_BUCKETS = 32;
+
+ private LuminanceSourceInterface $source;
+ private array $luminances;
+
+ /**
+ *
+ */
+ public function __construct(LuminanceSourceInterface $source){
+ $this->source = $source;
+ $this->luminances = $this->source->getLuminances();
+ }
+
+ /**
+ * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
+ */
+ private function estimateBlackPoint(array $buckets):int{
+ // Find the tallest peak in the histogram.
+ $numBuckets = count($buckets);
+ $maxBucketCount = 0;
+ $firstPeak = 0;
+ $firstPeakSize = 0;
+
+ for($x = 0; $x < $numBuckets; $x++){
+
+ if($buckets[$x] > $firstPeakSize){
+ $firstPeak = $x;
+ $firstPeakSize = $buckets[$x];
+ }
+
+ if($buckets[$x] > $maxBucketCount){
+ $maxBucketCount = $buckets[$x];
+ }
+ }
+
+ // Find the second-tallest peak which is somewhat far from the tallest peak.
+ $secondPeak = 0;
+ $secondPeakScore = 0;
+
+ for($x = 0; $x < $numBuckets; $x++){
+ $distanceToBiggest = ($x - $firstPeak);
+ // Encourage more distant second peaks by multiplying by square of distance.
+ $score = ($buckets[$x] * $distanceToBiggest * $distanceToBiggest);
+
+ if($score > $secondPeakScore){
+ $secondPeak = $x;
+ $secondPeakScore = $score;
+ }
+ }
+
+ // Make sure firstPeak corresponds to the black peak.
+ if($firstPeak > $secondPeak){
+ $temp = $firstPeak;
+ $firstPeak = $secondPeak;
+ $secondPeak = $temp;
+ }
+
+ // If there is too little contrast in the image to pick a meaningful black point, throw rather
+ // than waste time trying to decode the image, and risk false positives.
+ if(($secondPeak - $firstPeak) <= ($numBuckets / 16)){
+ throw new QRCodeDecoderException('no meaningful dark point found'); // @codeCoverageIgnore
+ }
+
+ // Find a valley between them that is low and closer to the white peak.
+ $bestValley = ($secondPeak - 1);
+ $bestValleyScore = -1;
+
+ for($x = ($secondPeak - 1); $x > $firstPeak; $x--){
+ $fromFirst = ($x - $firstPeak);
+ $score = ($fromFirst * $fromFirst * ($secondPeak - $x) * ($maxBucketCount - $buckets[$x]));
+
+ if($score > $bestValleyScore){
+ $bestValley = $x;
+ $bestValleyScore = $score;
+ }
+ }
+
+ return ($bestValley << self::LUMINANCE_SHIFT);
+ }
+
+ /**
+ * Calculates the final BitMatrix once for all requests. This could be called once from the
+ * constructor instead, but there are some advantages to doing it lazily, such as making
+ * profiling easier, and not doing heavy lifting when callers don't expect it.
+ *
+ * Converts a 2D array of luminance data to 1 bit data. As above, assume this method is expensive
+ * and do not call it repeatedly. This method is intended for decoding 2D barcodes and may or
+ * may not apply sharpening. Therefore, a row from this matrix may not be identical to one
+ * fetched using getBlackRow(), so don't mix and match between them.
+ *
+ * @return \chillerlan\QRCode\Decoder\BitMatrix The 2D array of bits for the image (true means black).
+ */
+ public function getBlackMatrix():BitMatrix{
+ $width = $this->source->getWidth();
+ $height = $this->source->getHeight();
+
+ if($width >= self::MINIMUM_DIMENSION && $height >= self::MINIMUM_DIMENSION){
+ $subWidth = ($width >> self::BLOCK_SIZE_POWER);
+
+ if(($width & self::BLOCK_SIZE_MASK) !== 0){
+ $subWidth++;
+ }
+
+ $subHeight = ($height >> self::BLOCK_SIZE_POWER);
+
+ if(($height & self::BLOCK_SIZE_MASK) !== 0){
+ $subHeight++;
+ }
+
+ return $this->calculateThresholdForBlock($subWidth, $subHeight, $width, $height);
+ }
+
+ // If the image is too small, fall back to the global histogram approach.
+ return $this->getHistogramBlackMatrix($width, $height);
+ }
+
+ /**
+ *
+ */
+ private function getHistogramBlackMatrix(int $width, int $height):BitMatrix{
+
+ // Quickly calculates the histogram by sampling four rows from the image. This proved to be
+ // more robust on the blackbox tests than sampling a diagonal as we used to do.
+ $buckets = array_fill(0, self::LUMINANCE_BUCKETS, 0);
+ $right = intdiv(($width * 4), 5);
+ $x = intdiv($width, 5);
+
+ for($y = 1; $y < 5; $y++){
+ $row = intdiv(($height * $y), 5);
+ $localLuminances = $this->source->getRow($row);
+
+ for(; $x < $right; $x++){
+ $pixel = ($localLuminances[$x] & 0xff);
+ $buckets[($pixel >> self::LUMINANCE_SHIFT)]++;
+ }
+ }
+
+ $blackPoint = $this->estimateBlackPoint($buckets);
+
+ // We delay reading the entire image luminance until the black point estimation succeeds.
+ // Although we end up reading four rows twice, it is consistent with our motto of
+ // "fail quickly" which is necessary for continuous scanning.
+ $matrix = new BitMatrix(max($width, $height));
+
+ for($y = 0; $y < $height; $y++){
+ $offset = ($y * $width);
+
+ for($x = 0; $x < $width; $x++){
+ $matrix->set($x, $y, (($this->luminances[($offset + $x)] & 0xff) < $blackPoint), QRMatrix::M_DATA);
+ }
+ }
+
+ return $matrix;
+ }
+
+ /**
+ * Calculates a single black point for each block of pixels and saves it away.
+ * See the following thread for a discussion of this algorithm:
+ *
+ * @see http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0
+ */
+ private function calculateBlackPoints(int $subWidth, int $subHeight, int $width, int $height):array{
+ $blackPoints = array_fill(0, $subHeight, array_fill(0, $subWidth, 0));
+
+ for($y = 0; $y < $subHeight; $y++){
+ $yoffset = ($y << self::BLOCK_SIZE_POWER);
+ $maxYOffset = ($height - self::BLOCK_SIZE);
+
+ if($yoffset > $maxYOffset){
+ $yoffset = $maxYOffset;
+ }
+
+ for($x = 0; $x < $subWidth; $x++){
+ $xoffset = ($x << self::BLOCK_SIZE_POWER);
+ $maxXOffset = ($width - self::BLOCK_SIZE);
+
+ if($xoffset > $maxXOffset){
+ $xoffset = $maxXOffset;
+ }
+
+ $sum = 0;
+ $min = 255;
+ $max = 0;
+
+ for($yy = 0, $offset = ($yoffset * $width + $xoffset); $yy < self::BLOCK_SIZE; $yy++, $offset += $width){
+
+ for($xx = 0; $xx < self::BLOCK_SIZE; $xx++){
+ $pixel = ((int)($this->luminances[(int)($offset + $xx)]) & 0xff);
+ $sum += $pixel;
+ // still looking for good contrast
+ if($pixel < $min){
+ $min = $pixel;
+ }
+
+ if($pixel > $max){
+ $max = $pixel;
+ }
+ }
+
+ // short-circuit min/max tests once dynamic range is met
+ if(($max - $min) > self::MIN_DYNAMIC_RANGE){
+ // finish the rest of the rows quickly
+ for($yy++, $offset += $width; $yy < self::BLOCK_SIZE; $yy++, $offset += $width){
+ for($xx = 0; $xx < self::BLOCK_SIZE; $xx++){
+ $sum += ((int)($this->luminances[(int)($offset + $xx)]) & 0xff);
+ }
+ }
+ }
+ }
+
+ // The default estimate is the average of the values in the block.
+ $average = ($sum >> (self::BLOCK_SIZE_POWER * 2));
+
+ if(($max - $min) <= self::MIN_DYNAMIC_RANGE){
+ // If variation within the block is low, assume this is a block with only light or only
+ // dark pixels. In that case we do not want to use the average, as it would divide this
+ // low contrast area into black and white pixels, essentially creating data out of noise.
+ //
+ // The default assumption is that the block is light/background. Since no estimate for
+ // the level of dark pixels exists locally, use half the min for the block.
+ $average = ($min / 2);
+
+ if($y > 0 && $x > 0){
+ // Correct the "white background" assumption for blocks that have neighbors by comparing
+ // the pixels in this block to the previously calculated black points. This is based on
+ // the fact that dark barcode symbology is always surrounded by some amount of light
+ // background for which reasonable black point estimates were made. The bp estimated at
+ // the boundaries is used for the interior.
+
+ // The (min < bp) is arbitrary but works better than other heuristics that were tried.
+ $averageNeighborBlackPoint = (
+ ($blackPoints[($y - 1)][$x] + (2 * $blackPoints[$y][($x - 1)]) + $blackPoints[($y - 1)][($x - 1)]) / 4
+ );
+
+ if($min < $averageNeighborBlackPoint){
+ $average = $averageNeighborBlackPoint;
+ }
+ }
+ }
+
+ $blackPoints[$y][$x] = $average;
+ }
+ }
+
+ return $blackPoints;
+ }
+
+ /**
+ * For each block in the image, calculate the average black point using a 5x5 grid
+ * of the surrounding blocks. Also handles the corner cases (fractional blocks are computed based
+ * on the last pixels in the row/column which are also used in the previous block).
+ */
+ private function calculateThresholdForBlock(int $subWidth, int $subHeight, int $width, int $height):BitMatrix{
+ $matrix = new BitMatrix(max($width, $height));
+ $blackPoints = $this->calculateBlackPoints($subWidth, $subHeight, $width, $height);
+
+ for($y = 0; $y < $subHeight; $y++){
+ $yoffset = ($y << self::BLOCK_SIZE_POWER);
+ $maxYOffset = ($height - self::BLOCK_SIZE);
+
+ if($yoffset > $maxYOffset){
+ $yoffset = $maxYOffset;
+ }
+
+ for($x = 0; $x < $subWidth; $x++){
+ $xoffset = ($x << self::BLOCK_SIZE_POWER);
+ $maxXOffset = ($width - self::BLOCK_SIZE);
+
+ if($xoffset > $maxXOffset){
+ $xoffset = $maxXOffset;
+ }
+
+ $left = $this->cap($x, 2, ($subWidth - 3));
+ $top = $this->cap($y, 2, ($subHeight - 3));
+ $sum = 0;
+
+ for($z = -2; $z <= 2; $z++){
+ $br = $blackPoints[($top + $z)];
+ $sum += ($br[($left - 2)] + $br[($left - 1)] + $br[$left] + $br[($left + 1)] + $br[($left + 2)]);
+ }
+
+ $average = (int)($sum / 25);
+
+ // Applies a single threshold to a block of pixels.
+ for($j = 0, $o = ($yoffset * $width + $xoffset); $j < self::BLOCK_SIZE; $j++, $o += $width){
+ for($i = 0; $i < self::BLOCK_SIZE; $i++){
+ // Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0.
+ $v = (((int)($this->luminances[($o + $i)]) & 0xff) <= $average);
+
+ $matrix->set(($xoffset + $i), ($yoffset + $j), $v, QRMatrix::M_DATA);
+ }
+ }
+ }
+ }
+
+ return $matrix;
+ }
+
+ /**
+ * @noinspection PhpSameParameterValueInspection
+ */
+ private function cap(int $value, int $min, int $max):int{
+
+ if($value < $min){
+ return $min;
+ }
+
+ if($value > $max){
+ return $max;
+ }
+
+ return $value;
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Decoder/BitMatrix.php b/vendor/chillerlan/php-qrcode/src/Decoder/BitMatrix.php
new file mode 100644
index 000000000..21f504e72
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Decoder/BitMatrix.php
@@ -0,0 +1,430 @@
+<?php
+/**
+ * Class BitMatrix
+ *
+ * @created 17.01.2021
+ * @author ZXing Authors
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license Apache-2.0
+ */
+
+namespace chillerlan\QRCode\Decoder;
+
+use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Version};
+use chillerlan\QRCode\Data\{QRCodeDataException, QRMatrix};
+use function array_fill, array_reverse, count;
+use const PHP_INT_MAX, PHP_INT_SIZE;
+
+/**
+ * Extended QRMatrix to map read data from the Binarizer
+ */
+final class BitMatrix extends QRMatrix{
+
+ /**
+ * See ISO 18004:2006, Annex C, Table C.1
+ *
+ * [data bits, sequence after masking]
+ */
+ private const DECODE_LOOKUP = [
+ 0x5412, // 0101010000010010
+ 0x5125, // 0101000100100101
+ 0x5E7C, // 0101111001111100
+ 0x5B4B, // 0101101101001011
+ 0x45F9, // 0100010111111001
+ 0x40CE, // 0100000011001110
+ 0x4F97, // 0100111110010111
+ 0x4AA0, // 0100101010100000
+ 0x77C4, // 0111011111000100
+ 0x72F3, // 0111001011110011
+ 0x7DAA, // 0111110110101010
+ 0x789D, // 0111100010011101
+ 0x662F, // 0110011000101111
+ 0x6318, // 0110001100011000
+ 0x6C41, // 0110110001000001
+ 0x6976, // 0110100101110110
+ 0x1689, // 0001011010001001
+ 0x13BE, // 0001001110111110
+ 0x1CE7, // 0001110011100111
+ 0x19D0, // 0001100111010000
+ 0x0762, // 0000011101100010
+ 0x0255, // 0000001001010101
+ 0x0D0C, // 0000110100001100
+ 0x083B, // 0000100000111011
+ 0x355F, // 0011010101011111
+ 0x3068, // 0011000001101000
+ 0x3F31, // 0011111100110001
+ 0x3A06, // 0011101000000110
+ 0x24B4, // 0010010010110100
+ 0x2183, // 0010000110000011
+ 0x2EDA, // 0010111011011010
+ 0x2BED, // 0010101111101101
+ ];
+
+ private const FORMAT_INFO_MASK_QR = 0x5412; // 0101010000010010
+
+ /**
+ * This flag has effect only on the copyVersionBit() method.
+ * Before proceeding with readCodewords() the resetInfo() method should be called.
+ */
+ private bool $mirror = false;
+
+ /**
+ * @noinspection PhpMissingParentConstructorInspection
+ */
+ public function __construct(int $dimension){
+ $this->moduleCount = $dimension;
+ $this->matrix = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, $this::M_NULL));
+ }
+
+ /**
+ * Resets the current version info in order to attempt another reading
+ */
+ public function resetVersionInfo():self{
+ $this->version = null;
+ $this->eccLevel = null;
+ $this->maskPattern = null;
+
+ return $this;
+ }
+
+ /**
+ * Mirror the bit matrix diagonally in order to attempt a second reading.
+ */
+ public function mirrorDiagonal():self{
+ $this->mirror = !$this->mirror;
+
+ // mirror vertically
+ $this->matrix = array_reverse($this->matrix);
+ // rotate by 90 degrees clockwise
+ /** @phan-suppress-next-line PhanTypeMismatchReturnSuperType */
+ return $this->rotate90();
+ }
+
+ /**
+ * Reads the bits in the BitMatrix representing the finder pattern in the
+ * correct order in order to reconstruct the codewords bytes contained within the
+ * QR Code. Throws if the exact number of bytes expected is not read.
+ *
+ * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
+ */
+ public function readCodewords():array{
+
+ $this
+ ->readFormatInformation()
+ ->readVersion()
+ ->mask($this->maskPattern) // reverse the mask pattern
+ ;
+
+ // invoke a fresh matrix with only the function & format patterns to compare against
+ $matrix = (new QRMatrix($this->version, $this->eccLevel))
+ ->initFunctionalPatterns()
+ ->setFormatInfo($this->maskPattern)
+ ;
+
+ $result = [];
+ $byte = 0;
+ $bitsRead = 0;
+ $direction = true;
+
+ // Read columns in pairs, from right to left
+ for($i = ($this->moduleCount - 1); $i > 0; $i -= 2){
+
+ // Skip whole column with vertical alignment pattern;
+ // saves time and makes the other code proceed more cleanly
+ if($i === 6){
+ $i--;
+ }
+ // Read alternatingly from bottom to top then top to bottom
+ for($count = 0; $count < $this->moduleCount; $count++){
+ $y = ($direction) ? ($this->moduleCount - 1 - $count) : $count;
+
+ for($col = 0; $col < 2; $col++){
+ $x = ($i - $col);
+
+ // Ignore bits covered by the function pattern
+ if($matrix->get($x, $y) !== $this::M_NULL){
+ continue;
+ }
+
+ $bitsRead++;
+ $byte <<= 1;
+
+ if($this->check($x, $y)){
+ $byte |= 1;
+ }
+ // If we've made a whole byte, save it off
+ if($bitsRead === 8){
+ $result[] = $byte;
+ $bitsRead = 0;
+ $byte = 0;
+ }
+ }
+ }
+
+ $direction = !$direction; // switch directions
+ }
+
+ if(count($result) !== $this->version->getTotalCodewords()){
+ throw new QRCodeDecoderException('result count differs from total codewords for version');
+ }
+
+ // bytes encoded within the QR Code
+ return $result;
+ }
+
+ /**
+ * Reads format information from one of its two locations within the QR Code.
+ * Throws if both format information locations cannot be parsed as the valid encoding of format information.
+ *
+ * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
+ */
+ private function readFormatInformation():self{
+
+ if($this->eccLevel !== null && $this->maskPattern !== null){
+ return $this;
+ }
+
+ // Read top-left format info bits
+ $formatInfoBits1 = 0;
+
+ for($i = 0; $i < 6; $i++){
+ $formatInfoBits1 = $this->copyVersionBit($i, 8, $formatInfoBits1);
+ }
+
+ // ... and skip a bit in the timing pattern ...
+ $formatInfoBits1 = $this->copyVersionBit(7, 8, $formatInfoBits1);
+ $formatInfoBits1 = $this->copyVersionBit(8, 8, $formatInfoBits1);
+ $formatInfoBits1 = $this->copyVersionBit(8, 7, $formatInfoBits1);
+ // ... and skip a bit in the timing pattern ...
+ for($j = 5; $j >= 0; $j--){
+ $formatInfoBits1 = $this->copyVersionBit(8, $j, $formatInfoBits1);
+ }
+
+ // Read the top-right/bottom-left pattern too
+ $formatInfoBits2 = 0;
+ $jMin = ($this->moduleCount - 7);
+
+ for($j = ($this->moduleCount - 1); $j >= $jMin; $j--){
+ $formatInfoBits2 = $this->copyVersionBit(8, $j, $formatInfoBits2);
+ }
+
+ for($i = ($this->moduleCount - 8); $i < $this->moduleCount; $i++){
+ $formatInfoBits2 = $this->copyVersionBit($i, 8, $formatInfoBits2);
+ }
+
+ $formatInfo = $this->doDecodeFormatInformation($formatInfoBits1, $formatInfoBits2);
+
+ if($formatInfo === null){
+
+ // Should return null, but, some QR codes apparently do not mask this info.
+ // Try again by actually masking the pattern first.
+ $formatInfo = $this->doDecodeFormatInformation(
+ ($formatInfoBits1 ^ $this::FORMAT_INFO_MASK_QR),
+ ($formatInfoBits2 ^ $this::FORMAT_INFO_MASK_QR)
+ );
+
+ // still nothing???
+ if($formatInfo === null){
+ throw new QRCodeDecoderException('failed to read format info'); // @codeCoverageIgnore
+ }
+
+ }
+
+ $this->eccLevel = new EccLevel(($formatInfo >> 3) & 0x03); // Bits 3,4
+ $this->maskPattern = new MaskPattern($formatInfo & 0x07); // Bottom 3 bits
+
+ return $this;
+ }
+
+ /**
+ *
+ */
+ private function copyVersionBit(int $i, int $j, int $versionBits):int{
+
+ $bit = $this->mirror
+ ? $this->check($j, $i)
+ : $this->check($i, $j);
+
+ return ($bit) ? (($versionBits << 1) | 0x1) : ($versionBits << 1);
+ }
+
+ /**
+ * Returns information about the format it specifies, or null if it doesn't seem to match any known pattern
+ */
+ private function doDecodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2):?int{
+ $bestDifference = PHP_INT_MAX;
+ $bestFormatInfo = 0;
+
+ // Find the int in FORMAT_INFO_DECODE_LOOKUP with the fewest bits differing
+ foreach($this::DECODE_LOOKUP as $maskedBits => $dataBits){
+
+ if($maskedFormatInfo1 === $dataBits || $maskedFormatInfo2 === $dataBits){
+ // Found an exact match
+ return $maskedBits;
+ }
+
+ $bitsDifference = $this->numBitsDiffering($maskedFormatInfo1, $dataBits);
+
+ if($bitsDifference < $bestDifference){
+ $bestFormatInfo = $maskedBits;
+ $bestDifference = $bitsDifference;
+ }
+
+ if($maskedFormatInfo1 !== $maskedFormatInfo2){
+ // also try the other option
+ $bitsDifference = $this->numBitsDiffering($maskedFormatInfo2, $dataBits);
+
+ if($bitsDifference < $bestDifference){
+ $bestFormatInfo = $maskedBits;
+ $bestDifference = $bitsDifference;
+ }
+ }
+ }
+ // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match
+ if($bestDifference <= 3){
+ return $bestFormatInfo;
+ }
+
+ return null;
+ }
+
+ /**
+ * Reads version information from one of its two locations within the QR Code.
+ * Throws if both version information locations cannot be parsed as the valid encoding of version information.
+ *
+ * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
+ * @noinspection DuplicatedCode
+ */
+ private function readVersion():self{
+
+ if($this->version !== null){
+ return $this;
+ }
+
+ $provisionalVersion = (($this->moduleCount - 17) / 4);
+
+ // no version info if v < 7
+ if($provisionalVersion < 7){
+ $this->version = new Version($provisionalVersion);
+
+ return $this;
+ }
+
+ // Read top-right version info: 3 wide by 6 tall
+ $versionBits = 0;
+ $ijMin = ($this->moduleCount - 11);
+
+ for($y = 5; $y >= 0; $y--){
+ for($x = ($this->moduleCount - 9); $x >= $ijMin; $x--){
+ $versionBits = $this->copyVersionBit($x, $y, $versionBits);
+ }
+ }
+
+ $this->version = $this->decodeVersionInformation($versionBits);
+
+ if($this->version !== null && $this->version->getDimension() === $this->moduleCount){
+ return $this;
+ }
+
+ // Hmm, failed. Try bottom left: 6 wide by 3 tall
+ $versionBits = 0;
+
+ for($x = 5; $x >= 0; $x--){
+ for($y = ($this->moduleCount - 9); $y >= $ijMin; $y--){
+ $versionBits = $this->copyVersionBit($x, $y, $versionBits);
+ }
+ }
+
+ $this->version = $this->decodeVersionInformation($versionBits);
+
+ if($this->version !== null && $this->version->getDimension() === $this->moduleCount){
+ return $this;
+ }
+
+ throw new QRCodeDecoderException('failed to read version');
+ }
+
+ /**
+ * Decodes the version information from the given bit sequence, returns null if no valid match is found.
+ */
+ private function decodeVersionInformation(int $versionBits):?Version{
+ $bestDifference = PHP_INT_MAX;
+ $bestVersion = 0;
+
+ for($i = 7; $i <= 40; $i++){
+ $targetVersion = new Version($i);
+ $targetVersionPattern = $targetVersion->getVersionPattern();
+
+ // Do the version info bits match exactly? done.
+ if($targetVersionPattern === $versionBits){
+ return $targetVersion;
+ }
+
+ // Otherwise see if this is the closest to a real version info bit string
+ // we have seen so far
+ /** @phan-suppress-next-line PhanTypeMismatchArgumentNullable ($targetVersionPattern is never null here) */
+ $bitsDifference = $this->numBitsDiffering($versionBits, $targetVersionPattern);
+
+ if($bitsDifference < $bestDifference){
+ $bestVersion = $i;
+ $bestDifference = $bitsDifference;
+ }
+ }
+ // We can tolerate up to 3 bits of error since no two version info codewords will
+ // differ in less than 8 bits.
+ if($bestDifference <= 3){
+ return new Version($bestVersion);
+ }
+
+ // If we didn't find a close enough match, fail
+ return null;
+ }
+
+ /**
+ *
+ */
+ private function uRShift(int $a, int $b):int{
+
+ if($b === 0){
+ return $a;
+ }
+
+ return (($a >> $b) & ~((1 << (8 * PHP_INT_SIZE - 1)) >> ($b - 1)));
+ }
+
+ /**
+ *
+ */
+ private function numBitsDiffering(int $a, int $b):int{
+ // a now has a 1 bit exactly where its bit differs with b's
+ $a ^= $b;
+ // Offset $i holds the number of 1-bits in the binary representation of $i
+ $BITS_SET_IN_HALF_BYTE = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4];
+ // Count bits set quickly with a series of lookups:
+ $count = 0;
+
+ for($i = 0; $i < 32; $i += 4){
+ $count += $BITS_SET_IN_HALF_BYTE[($this->uRShift($a, $i) & 0x0F)];
+ }
+
+ return $count;
+ }
+
+ /**
+ * @codeCoverageIgnore
+ * @throws \chillerlan\QRCode\Data\QRCodeDataException
+ */
+ public function setQuietZone(?int $quietZoneSize = null):self{
+ throw new QRCodeDataException('not supported');
+ }
+
+ /**
+ * @codeCoverageIgnore
+ * @throws \chillerlan\QRCode\Data\QRCodeDataException
+ */
+ public function setLogoSpace(int $width, ?int $height = null, ?int $startX = null, ?int $startY = null):self{
+ throw new QRCodeDataException('not supported');
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Decoder/Decoder.php b/vendor/chillerlan/php-qrcode/src/Decoder/Decoder.php
new file mode 100644
index 000000000..6f369a6df
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Decoder/Decoder.php
@@ -0,0 +1,173 @@
+<?php
+/**
+ * Class Decoder
+ *
+ * @created 17.01.2021
+ * @author ZXing Authors
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license Apache-2.0
+ */
+
+namespace chillerlan\QRCode\Decoder;
+
+use chillerlan\QRCode\Common\{BitBuffer, EccLevel, LuminanceSourceInterface, MaskPattern, Mode, Version};
+use chillerlan\QRCode\Data\{AlphaNum, Byte, ECI, Hanzi, Kanji, Number};
+use chillerlan\QRCode\Detector\Detector;
+use Throwable;
+use function chr, str_replace;
+
+/**
+ * The main class which implements QR Code decoding -- as opposed to locating and extracting
+ * the QR Code from an image.
+ *
+ * @author Sean Owen
+ */
+final class Decoder{
+
+ private ?Version $version = null;
+ private ?EccLevel $eccLevel = null;
+ private ?MaskPattern $maskPattern = null;
+ private BitBuffer $bitBuffer;
+
+ /**
+ * Decodes a QR Code represented as a BitMatrix.
+ * A 1 or "true" is taken to mean a black module.
+ *
+ * @throws \Throwable|\chillerlan\QRCode\Decoder\QRCodeDecoderException
+ */
+ public function decode(LuminanceSourceInterface $source):DecoderResult{
+ $matrix = (new Detector($source))->detect();
+
+ try{
+ // clone the BitMatrix to avoid errors in case we run into mirroring
+ return $this->decodeMatrix(clone $matrix);
+ }
+ catch(Throwable $e){
+
+ try{
+ /*
+ * Prepare for a mirrored reading.
+ *
+ * Since we're here, this means we have successfully detected some kind
+ * of version and format information when mirrored. This is a good sign,
+ * that the QR code may be mirrored, and we should try once more with a
+ * mirrored content.
+ */
+ return $this->decodeMatrix($matrix->resetVersionInfo()->mirrorDiagonal());
+ }
+ catch(Throwable $f){
+ // Throw the exception from the original reading
+ throw $e;
+ }
+
+ }
+
+ }
+
+ /**
+ * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
+ */
+ private function decodeMatrix(BitMatrix $matrix):DecoderResult{
+ // Read raw codewords
+ $rawCodewords = $matrix->readCodewords();
+ $this->version = $matrix->getVersion();
+ $this->eccLevel = $matrix->getEccLevel();
+ $this->maskPattern = $matrix->getMaskPattern();
+
+ if($this->version === null || $this->eccLevel === null || $this->maskPattern === null){
+ throw new QRCodeDecoderException('unable to read version or format info'); // @codeCoverageIgnore
+ }
+
+ $resultBytes = (new ReedSolomonDecoder($this->version, $this->eccLevel))->decode($rawCodewords);
+
+ return $this->decodeBitStream($resultBytes);
+ }
+
+ /**
+ * Decode the contents of that stream of bytes
+ *
+ * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
+ */
+ private function decodeBitStream(BitBuffer $bitBuffer):DecoderResult{
+ $this->bitBuffer = $bitBuffer;
+ $versionNumber = $this->version->getVersionNumber();
+ $symbolSequence = -1;
+ $parityData = -1;
+ $fc1InEffect = false;
+ $result = '';
+
+ // While still another segment to read...
+ while($this->bitBuffer->available() >= 4){
+ $datamode = $this->bitBuffer->read(4); // mode is encoded by 4 bits
+
+ // OK, assume we're done
+ if($datamode === Mode::TERMINATOR){
+ break;
+ }
+ elseif($datamode === Mode::NUMBER){
+ $result .= Number::decodeSegment($this->bitBuffer, $versionNumber);
+ }
+ elseif($datamode === Mode::ALPHANUM){
+ $result .= $this->decodeAlphanumSegment($versionNumber, $fc1InEffect);
+ }
+ elseif($datamode === Mode::BYTE){
+ $result .= Byte::decodeSegment($this->bitBuffer, $versionNumber);
+ }
+ elseif($datamode === Mode::KANJI){
+ $result .= Kanji::decodeSegment($this->bitBuffer, $versionNumber);
+ }
+ elseif($datamode === Mode::STRCTURED_APPEND){
+
+ if($this->bitBuffer->available() < 16){
+ throw new QRCodeDecoderException('structured append: not enough bits left');
+ }
+ // sequence number and parity is added later to the result metadata
+ // Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue
+ $symbolSequence = $this->bitBuffer->read(8);
+ $parityData = $this->bitBuffer->read(8);
+ }
+ elseif($datamode === Mode::FNC1_FIRST || $datamode === Mode::FNC1_SECOND){
+ // We do little with FNC1 except alter the parsed result a bit according to the spec
+ $fc1InEffect = true;
+ }
+ elseif($datamode === Mode::ECI){
+ $result .= ECI::decodeSegment($this->bitBuffer, $versionNumber);
+ }
+ elseif($datamode === Mode::HANZI){
+ $result .= Hanzi::decodeSegment($this->bitBuffer, $versionNumber);
+ }
+ else{
+ throw new QRCodeDecoderException('invalid data mode');
+ }
+
+ }
+
+ return new DecoderResult([
+ 'rawBytes' => $this->bitBuffer,
+ 'data' => $result,
+ 'version' => $this->version,
+ 'eccLevel' => $this->eccLevel,
+ 'maskPattern' => $this->maskPattern,
+ 'structuredAppendParity' => $parityData,
+ 'structuredAppendSequence' => $symbolSequence,
+ ]);
+ }
+
+ /**
+ *
+ */
+ private function decodeAlphanumSegment(int $versionNumber, bool $fc1InEffect):string{
+ $str = AlphaNum::decodeSegment($this->bitBuffer, $versionNumber);
+
+ // See section 6.4.8.1, 6.4.8.2
+ if($fc1InEffect){ // ???
+ // We need to massage the result a bit if in an FNC1 mode:
+ $str = str_replace(chr(0x1d), '%', $str);
+ $str = str_replace('%%', '%', $str);
+ }
+
+ return $str;
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php b/vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php
new file mode 100644
index 000000000..13fd24ba8
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Class DecoderResult
+ *
+ * @created 17.01.2021
+ * @author ZXing Authors
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license Apache-2.0
+ */
+
+namespace chillerlan\QRCode\Decoder;
+
+use chillerlan\QRCode\Common\{BitBuffer, EccLevel, MaskPattern, Version};
+use chillerlan\QRCode\Data\QRMatrix;
+use function property_exists;
+
+/**
+ * Encapsulates the result of decoding a matrix of bits. This typically
+ * applies to 2D barcode formats. For now, it contains the raw bytes obtained
+ * as well as a String interpretation of those bytes, if applicable.
+ *
+ * @property \chillerlan\QRCode\Common\BitBuffer $rawBytes
+ * @property string $data
+ * @property \chillerlan\QRCode\Common\Version $version
+ * @property \chillerlan\QRCode\Common\EccLevel $eccLevel
+ * @property \chillerlan\QRCode\Common\MaskPattern $maskPattern
+ * @property int $structuredAppendParity
+ * @property int $structuredAppendSequence
+ */
+final class DecoderResult{
+
+ private BitBuffer $rawBytes;
+ private Version $version;
+ private EccLevel $eccLevel;
+ private MaskPattern $maskPattern;
+ private string $data = '';
+ private int $structuredAppendParity = -1;
+ private int $structuredAppendSequence = -1;
+
+ /**
+ * DecoderResult constructor.
+ */
+ public function __construct(?iterable $properties = null){
+
+ if(!empty($properties)){
+
+ foreach($properties as $property => $value){
+
+ if(!property_exists($this, $property)){
+ continue;
+ }
+
+ $this->{$property} = $value;
+ }
+
+ }
+
+ }
+
+ /**
+ * @return mixed|null
+ */
+ public function __get(string $property){
+
+ if(property_exists($this, $property)){
+ return $this->{$property};
+ }
+
+ return null;
+ }
+
+ /**
+ *
+ */
+ public function __toString():string{
+ return $this->data;
+ }
+
+ /**
+ *
+ */
+ public function hasStructuredAppend():bool{
+ return $this->structuredAppendParity >= 0 && $this->structuredAppendSequence >= 0;
+ }
+
+ /**
+ * Returns a QRMatrix instance with the settings and data of the reader result
+ */
+ public function getQRMatrix():QRMatrix{
+ return (new QRMatrix($this->version, $this->eccLevel))
+ ->initFunctionalPatterns()
+ ->writeCodewords($this->rawBytes)
+ ->setFormatInfo($this->maskPattern)
+ ->mask($this->maskPattern)
+ ;
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Decoder/QRCodeDecoderException.php b/vendor/chillerlan/php-qrcode/src/Decoder/QRCodeDecoderException.php
new file mode 100644
index 000000000..11157afc1
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Decoder/QRCodeDecoderException.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * Class QRCodeDecoderException
+ *
+ * @created 01.12.2021
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2021 smiley
+ * @license MIT
+ */
+
+namespace chillerlan\QRCode\Decoder;
+
+use chillerlan\QRCode\QRCodeException;
+
+/**
+ * An exception container
+ */
+final class QRCodeDecoderException extends QRCodeException{
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Decoder/ReedSolomonDecoder.php b/vendor/chillerlan/php-qrcode/src/Decoder/ReedSolomonDecoder.php
new file mode 100644
index 000000000..5f104a1c8
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Decoder/ReedSolomonDecoder.php
@@ -0,0 +1,313 @@
+<?php
+/**
+ * Class ReedSolomonDecoder
+ *
+ * @created 24.01.2021
+ * @author ZXing Authors
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license Apache-2.0
+ */
+
+namespace chillerlan\QRCode\Decoder;
+
+use chillerlan\QRCode\Common\{BitBuffer, EccLevel, GenericGFPoly, GF256, Version};
+use function array_fill, array_reverse, count;
+
+/**
+ * Implements Reed-Solomon decoding
+ *
+ * The algorithm will not be explained here, but the following references were helpful
+ * in creating this implementation:
+ *
+ * - Bruce Maggs "Decoding Reed-Solomon Codes" (see discussion of Forney's Formula)
+ * http://www.cs.cmu.edu/afs/cs.cmu.edu/project/pscico-guyb/realworld/www/rs_decode.ps
+ * - J.I. Hall. "Chapter 5. Generalized Reed-Solomon Codes" (see discussion of Euclidean algorithm)
+ * https://users.math.msu.edu/users/halljo/classes/codenotes/GRS.pdf
+ *
+ * Much credit is due to William Rucklidge since portions of this code are an indirect
+ * port of his C++ Reed-Solomon implementation.
+ *
+ * @author Sean Owen
+ * @author William Rucklidge
+ * @author sanfordsquires
+ */
+final class ReedSolomonDecoder{
+
+ private Version $version;
+ private EccLevel $eccLevel;
+
+ /**
+ * ReedSolomonDecoder constructor
+ */
+ public function __construct(Version $version, EccLevel $eccLevel){
+ $this->version = $version;
+ $this->eccLevel = $eccLevel;
+ }
+
+ /**
+ * Error-correct and copy data blocks together into a stream of bytes
+ */
+ public function decode(array $rawCodewords):BitBuffer{
+ $dataBlocks = $this->deinterleaveRawBytes($rawCodewords);
+ $dataBytes = [];
+
+ foreach($dataBlocks as [$numDataCodewords, $codewordBytes]){
+ $corrected = $this->correctErrors($codewordBytes, $numDataCodewords);
+
+ for($i = 0; $i < $numDataCodewords; $i++){
+ $dataBytes[] = $corrected[$i];
+ }
+ }
+
+ return new BitBuffer($dataBytes);
+ }
+
+ /**
+ * When QR Codes use multiple data blocks, they are actually interleaved.
+ * That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This
+ * method will separate the data into original blocks.
+ *
+ * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
+ */
+ private function deinterleaveRawBytes(array $rawCodewords):array{
+ // Figure out the number and size of data blocks used by this version and
+ // error correction level
+ [$numEccCodewords, $eccBlocks] = $this->version->getRSBlocks($this->eccLevel);
+
+ // Now establish DataBlocks of the appropriate size and number of data codewords
+ $result = [];//new DataBlock[$totalBlocks];
+ $numResultBlocks = 0;
+
+ foreach($eccBlocks as [$numEccBlocks, $eccPerBlock]){
+ for($i = 0; $i < $numEccBlocks; $i++, $numResultBlocks++){
+ $result[$numResultBlocks] = [$eccPerBlock, array_fill(0, ($numEccCodewords + $eccPerBlock), 0)];
+ }
+ }
+
+ // All blocks have the same amount of data, except that the last n
+ // (where n may be 0) have 1 more byte. Figure out where these start.
+ /** @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset */
+ $shorterBlocksTotalCodewords = count($result[0][1]);
+ $longerBlocksStartAt = (count($result) - 1);
+
+ while($longerBlocksStartAt >= 0){
+ $numCodewords = count($result[$longerBlocksStartAt][1]);
+
+ if($numCodewords === $shorterBlocksTotalCodewords){
+ break;
+ }
+
+ $longerBlocksStartAt--;
+ }
+
+ $longerBlocksStartAt++;
+
+ $shorterBlocksNumDataCodewords = ($shorterBlocksTotalCodewords - $numEccCodewords);
+ // The last elements of result may be 1 element longer;
+ // first fill out as many elements as all of them have
+ $rawCodewordsOffset = 0;
+
+ for($i = 0; $i < $shorterBlocksNumDataCodewords; $i++){
+ for($j = 0; $j < $numResultBlocks; $j++){
+ $result[$j][1][$i] = $rawCodewords[$rawCodewordsOffset++];
+ }
+ }
+
+ // Fill out the last data block in the longer ones
+ for($j = $longerBlocksStartAt; $j < $numResultBlocks; $j++){
+ $result[$j][1][$shorterBlocksNumDataCodewords] = $rawCodewords[$rawCodewordsOffset++];
+ }
+
+ // Now add in error correction blocks
+ /** @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset */
+ $max = count($result[0][1]);
+
+ for($i = $shorterBlocksNumDataCodewords; $i < $max; $i++){
+ for($j = 0; $j < $numResultBlocks; $j++){
+ $iOffset = ($j < $longerBlocksStartAt) ? $i : ($i + 1);
+ $result[$j][1][$iOffset] = $rawCodewords[$rawCodewordsOffset++];
+ }
+ }
+
+ // DataBlocks containing original bytes, "de-interleaved" from representation in the QR Code
+ return $result;
+ }
+
+ /**
+ * Given data and error-correction codewords received, possibly corrupted by errors, attempts to
+ * correct the errors in-place using Reed-Solomon error correction.
+ */
+ private function correctErrors(array $codewordBytes, int $numDataCodewords):array{
+ // First read into an array of ints
+ $codewordsInts = [];
+
+ foreach($codewordBytes as $codewordByte){
+ $codewordsInts[] = ($codewordByte & 0xFF);
+ }
+
+ $decoded = $this->decodeWords($codewordsInts, (count($codewordBytes) - $numDataCodewords));
+
+ // Copy back into array of bytes -- only need to worry about the bytes that were data
+ // We don't care about errors in the error-correction codewords
+ for($i = 0; $i < $numDataCodewords; $i++){
+ $codewordBytes[$i] = $decoded[$i];
+ }
+
+ return $codewordBytes;
+ }
+
+ /**
+ * Decodes given set of received codewords, which include both data and error-correction
+ * codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place,
+ * in the input.
+ *
+ * @param array $received data and error-correction codewords
+ * @param int $numEccCodewords number of error-correction codewords available
+ *
+ * @return int[]
+ * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException if decoding fails for any reason
+ */
+ private function decodeWords(array $received, int $numEccCodewords):array{
+ $poly = new GenericGFPoly($received);
+ $syndromeCoefficients = [];
+ $error = false;
+
+ for($i = 0; $i < $numEccCodewords; $i++){
+ $syndromeCoefficients[$i] = $poly->evaluateAt(GF256::exp($i));
+
+ if($syndromeCoefficients[$i] !== 0){
+ $error = true;
+ }
+ }
+
+ if(!$error){
+ return $received;
+ }
+
+ [$sigma, $omega] = $this->runEuclideanAlgorithm(
+ GF256::buildMonomial($numEccCodewords, 1),
+ new GenericGFPoly(array_reverse($syndromeCoefficients)),
+ $numEccCodewords
+ );
+
+ $errorLocations = $this->findErrorLocations($sigma);
+ $errorMagnitudes = $this->findErrorMagnitudes($omega, $errorLocations);
+ $errorLocationsCount = count($errorLocations);
+ $receivedCount = count($received);
+
+ for($i = 0; $i < $errorLocationsCount; $i++){
+ $position = ($receivedCount - 1 - GF256::log($errorLocations[$i]));
+
+ if($position < 0){
+ throw new QRCodeDecoderException('Bad error location');
+ }
+
+ $received[$position] ^= $errorMagnitudes[$i];
+ }
+
+ return $received;
+ }
+
+ /**
+ * @return \chillerlan\QRCode\Common\GenericGFPoly[] [sigma, omega]
+ * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
+ */
+ private function runEuclideanAlgorithm(GenericGFPoly $a, GenericGFPoly $b, int $z):array{
+ // Assume a's degree is >= b's
+ if($a->getDegree() < $b->getDegree()){
+ $temp = $a;
+ $a = $b;
+ $b = $temp;
+ }
+
+ $rLast = $a;
+ $r = $b;
+ $tLast = new GenericGFPoly([0]);
+ $t = new GenericGFPoly([1]);
+
+ // Run Euclidean algorithm until r's degree is less than z/2
+ while((2 * $r->getDegree()) >= $z){
+ $rLastLast = $rLast;
+ $tLastLast = $tLast;
+ $rLast = $r;
+ $tLast = $t;
+
+ // Divide rLastLast by rLast, with quotient in q and remainder in r
+ [$q, $r] = $rLastLast->divide($rLast);
+
+ $t = $q->multiply($tLast)->addOrSubtract($tLastLast);
+
+ if($r->getDegree() >= $rLast->getDegree()){
+ throw new QRCodeDecoderException('Division algorithm failed to reduce polynomial?');
+ }
+ }
+
+ $sigmaTildeAtZero = $t->getCoefficient(0);
+
+ if($sigmaTildeAtZero === 0){
+ throw new QRCodeDecoderException('sigmaTilde(0) was zero');
+ }
+
+ $inverse = GF256::inverse($sigmaTildeAtZero);
+
+ return [$t->multiplyInt($inverse), $r->multiplyInt($inverse)];
+ }
+
+ /**
+ * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
+ */
+ private function findErrorLocations(GenericGFPoly $errorLocator):array{
+ // This is a direct application of Chien's search
+ $numErrors = $errorLocator->getDegree();
+
+ if($numErrors === 1){ // shortcut
+ return [$errorLocator->getCoefficient(1)];
+ }
+
+ $result = array_fill(0, $numErrors, 0);
+ $e = 0;
+
+ for($i = 1; $i < 256 && $e < $numErrors; $i++){
+ if($errorLocator->evaluateAt($i) === 0){
+ $result[$e] = GF256::inverse($i);
+ $e++;
+ }
+ }
+
+ if($e !== $numErrors){
+ throw new QRCodeDecoderException('Error locator degree does not match number of roots');
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ */
+ private function findErrorMagnitudes(GenericGFPoly $errorEvaluator, array $errorLocations):array{
+ // This is directly applying Forney's Formula
+ $s = count($errorLocations);
+ $result = [];
+
+ for($i = 0; $i < $s; $i++){
+ $xiInverse = GF256::inverse($errorLocations[$i]);
+ $denominator = 1;
+
+ for($j = 0; $j < $s; $j++){
+ if($i !== $j){
+# $denominator = GF256::multiply($denominator, GF256::addOrSubtract(1, GF256::multiply($errorLocations[$j], $xiInverse)));
+ // Above should work but fails on some Apple and Linux JDKs due to a Hotspot bug.
+ // Below is a funny-looking workaround from Steven Parkes
+ $term = GF256::multiply($errorLocations[$j], $xiInverse);
+ $denominator = GF256::multiply($denominator, ((($term & 0x1) === 0) ? ($term | 1) : ($term & ~1)));
+ }
+ }
+
+ $result[$i] = GF256::multiply($errorEvaluator->evaluateAt($xiInverse), GF256::inverse($denominator));
+ }
+
+ return $result;
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php b/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php
new file mode 100644
index 000000000..72feafdfa
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Class AlignmentPattern
+ *
+ * @created 17.01.2021
+ * @author ZXing Authors
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license Apache-2.0
+ */
+
+namespace chillerlan\QRCode\Detector;
+
+/**
+ * Encapsulates an alignment pattern, which are the smaller square patterns found in
+ * all but the simplest QR Codes.
+ *
+ * @author Sean Owen
+ */
+final class AlignmentPattern extends ResultPoint{
+
+ /**
+ * Combines this object's current estimate of a finder pattern position and module size
+ * with a new estimate. It returns a new FinderPattern containing an average of the two.
+ */
+ public function combineEstimate(float $i, float $j, float $newModuleSize):self{
+ return new self(
+ (($this->x + $j) / 2.0),
+ (($this->y + $i) / 2.0),
+ (($this->estimatedModuleSize + $newModuleSize) / 2.0)
+ );
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php b/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php
new file mode 100644
index 000000000..d9edc50bb
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php
@@ -0,0 +1,284 @@
+<?php
+/**
+ * Class AlignmentPatternFinder
+ *
+ * @created 17.01.2021
+ * @author ZXing Authors
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license Apache-2.0
+ */
+
+namespace chillerlan\QRCode\Detector;
+
+use chillerlan\QRCode\Decoder\BitMatrix;
+use function abs, count;
+
+/**
+ * This class attempts to find alignment patterns in a QR Code. Alignment patterns look like finder
+ * patterns but are smaller and appear at regular intervals throughout the image.
+ *
+ * At the moment this only looks for the bottom-right alignment pattern.
+ *
+ * This is mostly a simplified copy of FinderPatternFinder. It is copied,
+ * pasted and stripped down here for maximum performance but does unfortunately duplicate
+ * some code.
+ *
+ * This class is thread-safe but not reentrant. Each thread must allocate its own object.
+ *
+ * @author Sean Owen
+ */
+final class AlignmentPatternFinder{
+
+ private BitMatrix $matrix;
+ private float $moduleSize;
+ /** @var \chillerlan\QRCode\Detector\AlignmentPattern[] */
+ private array $possibleCenters;
+
+ /**
+ * Creates a finder that will look in a portion of the whole image.
+ *
+ * @param \chillerlan\QRCode\Decoder\BitMatrix $matrix image to search
+ * @param float $moduleSize estimated module size so far
+ */
+ public function __construct(BitMatrix $matrix, float $moduleSize){
+ $this->matrix = $matrix;
+ $this->moduleSize = $moduleSize;
+ $this->possibleCenters = [];
+ }
+
+ /**
+ * This method attempts to find the bottom-right alignment pattern in the image. It is a bit messy since
+ * it's pretty performance-critical and so is written to be fast foremost.
+ *
+ * @param int $startX left column from which to start searching
+ * @param int $startY top row from which to start searching
+ * @param int $width width of region to search
+ * @param int $height height of region to search
+ *
+ * @return \chillerlan\QRCode\Detector\AlignmentPattern|null
+ */
+ public function find(int $startX, int $startY, int $width, int $height):?AlignmentPattern{
+ $maxJ = ($startX + $width);
+ $middleI = ($startY + ($height / 2));
+ $stateCount = [];
+
+ // We are looking for black/white/black modules in 1:1:1 ratio;
+ // this tracks the number of black/white/black modules seen so far
+ for($iGen = 0; $iGen < $height; $iGen++){
+ // Search from middle outwards
+ $i = (int)($middleI + ((($iGen & 0x01) === 0) ? ($iGen + 1) / 2 : -(($iGen + 1) / 2)));
+ $stateCount[0] = 0;
+ $stateCount[1] = 0;
+ $stateCount[2] = 0;
+ $j = $startX;
+ // Burn off leading white pixels before anything else; if we start in the middle of
+ // a white run, it doesn't make sense to count its length, since we don't know if the
+ // white run continued to the left of the start point
+ while($j < $maxJ && !$this->matrix->check($j, $i)){
+ $j++;
+ }
+
+ $currentState = 0;
+
+ while($j < $maxJ){
+
+ if($this->matrix->check($j, $i)){
+ // Black pixel
+ if($currentState === 1){ // Counting black pixels
+ $stateCount[$currentState]++;
+ }
+ // Counting white pixels
+ else{
+ // A winner?
+ if($currentState === 2){
+ // Yes
+ if($this->foundPatternCross($stateCount)){
+ $confirmed = $this->handlePossibleCenter($stateCount, $i, $j);
+
+ if($confirmed !== null){
+ return $confirmed;
+ }
+ }
+
+ $stateCount[0] = $stateCount[2];
+ $stateCount[1] = 1;
+ $stateCount[2] = 0;
+ $currentState = 1;
+ }
+ else{
+ $stateCount[++$currentState]++;
+ }
+ }
+ }
+ // White pixel
+ else{
+ // Counting black pixels
+ if($currentState === 1){
+ $currentState++;
+ }
+
+ $stateCount[$currentState]++;
+ }
+
+ $j++;
+ }
+
+ if($this->foundPatternCross($stateCount)){
+ $confirmed = $this->handlePossibleCenter($stateCount, $i, $maxJ);
+
+ if($confirmed !== null){
+ return $confirmed;
+ }
+ }
+
+ }
+
+ // Hmm, nothing we saw was observed and confirmed twice. If we had
+ // any guess at all, return it.
+ if(count($this->possibleCenters)){
+ return $this->possibleCenters[0];
+ }
+
+ return null;
+ }
+
+ /**
+ * @param int[] $stateCount count of black/white/black pixels just read
+ *
+ * @return bool true if the proportions of the counts is close enough to the 1/1/1 ratios
+ * used by alignment patterns to be considered a match
+ */
+ private function foundPatternCross(array $stateCount):bool{
+ $maxVariance = ($this->moduleSize / 2.0);
+
+ for($i = 0; $i < 3; $i++){
+ if(abs($this->moduleSize - $stateCount[$i]) >= $maxVariance){
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * This is called when a horizontal scan finds a possible alignment pattern. It will
+ * cross-check with a vertical scan, and if successful, will see if this pattern had been
+ * found on a previous horizontal scan. If so, we consider it confirmed and conclude we have
+ * found the alignment pattern.
+ *
+ * @param int[] $stateCount reading state module counts from horizontal scan
+ * @param int $i row where alignment pattern may be found
+ * @param int $j end of possible alignment pattern in row
+ *
+ * @return \chillerlan\QRCode\Detector\AlignmentPattern|null if we have found the same pattern twice, or null if not
+ */
+ private function handlePossibleCenter(array $stateCount, int $i, int $j):?AlignmentPattern{
+ $stateCountTotal = ($stateCount[0] + $stateCount[1] + $stateCount[2]);
+ $centerJ = $this->centerFromEnd($stateCount, $j);
+ $centerI = $this->crossCheckVertical($i, (int)$centerJ, (2 * $stateCount[1]), $stateCountTotal);
+
+ if($centerI !== null){
+ $estimatedModuleSize = (($stateCount[0] + $stateCount[1] + $stateCount[2]) / 3.0);
+
+ foreach($this->possibleCenters as $center){
+ // Look for about the same center and module size:
+ if($center->aboutEquals($estimatedModuleSize, $centerI, $centerJ)){
+ return $center->combineEstimate($centerI, $centerJ, $estimatedModuleSize);
+ }
+ }
+
+ // Hadn't found this before; save it
+ $point = new AlignmentPattern($centerJ, $centerI, $estimatedModuleSize);
+ $this->possibleCenters[] = $point;
+ }
+
+ return null;
+ }
+
+ /**
+ * Given a count of black/white/black pixels just seen and an end position,
+ * figures the location of the center of this black/white/black run.
+ *
+ * @param int[] $stateCount
+ * @param int $end
+ *
+ * @return float
+ */
+ private function centerFromEnd(array $stateCount, int $end):float{
+ return (float)(($end - $stateCount[2]) - $stateCount[1] / 2);
+ }
+
+ /**
+ * After a horizontal scan finds a potential alignment pattern, this method
+ * "cross-checks" by scanning down vertically through the center of the possible
+ * alignment pattern to see if the same proportion is detected.
+ *
+ * @param int $startI row where an alignment pattern was detected
+ * @param int $centerJ center of the section that appears to cross an alignment pattern
+ * @param int $maxCount maximum reasonable number of modules that should be
+ * observed in any reading state, based on the results of the horizontal scan
+ * @param int $originalStateCountTotal
+ *
+ * @return float|null vertical center of alignment pattern, or null if not found
+ */
+ private function crossCheckVertical(int $startI, int $centerJ, int $maxCount, int $originalStateCountTotal):?float{
+ $maxI = $this->matrix->getSize();
+ $stateCount = [];
+ $stateCount[0] = 0;
+ $stateCount[1] = 0;
+ $stateCount[2] = 0;
+
+ // Start counting up from center
+ $i = $startI;
+ while($i >= 0 && $this->matrix->check($centerJ, $i) && $stateCount[1] <= $maxCount){
+ $stateCount[1]++;
+ $i--;
+ }
+ // If already too many modules in this state or ran off the edge:
+ if($i < 0 || $stateCount[1] > $maxCount){
+ return null;
+ }
+
+ while($i >= 0 && !$this->matrix->check($centerJ, $i) && $stateCount[0] <= $maxCount){
+ $stateCount[0]++;
+ $i--;
+ }
+
+ if($stateCount[0] > $maxCount){
+ return null;
+ }
+
+ // Now also count down from center
+ $i = ($startI + 1);
+ while($i < $maxI && $this->matrix->check($centerJ, $i) && $stateCount[1] <= $maxCount){
+ $stateCount[1]++;
+ $i++;
+ }
+
+ if($i === $maxI || $stateCount[1] > $maxCount){
+ return null;
+ }
+
+ while($i < $maxI && !$this->matrix->check($centerJ, $i) && $stateCount[2] <= $maxCount){
+ $stateCount[2]++;
+ $i++;
+ }
+
+ if($stateCount[2] > $maxCount){
+ return null;
+ }
+
+ // phpcs:ignore
+ if((5 * abs(($stateCount[0] + $stateCount[1] + $stateCount[2]) - $originalStateCountTotal)) >= (2 * $originalStateCountTotal)){
+ return null;
+ }
+
+ if(!$this->foundPatternCross($stateCount)){
+ return null;
+ }
+
+ return $this->centerFromEnd($stateCount, $i);
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Detector/Detector.php b/vendor/chillerlan/php-qrcode/src/Detector/Detector.php
new file mode 100644
index 000000000..123b685c6
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Detector/Detector.php
@@ -0,0 +1,350 @@
+<?php
+/**
+ * Class Detector
+ *
+ * @created 17.01.2021
+ * @author ZXing Authors
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license Apache-2.0
+ */
+
+namespace chillerlan\QRCode\Detector;
+
+use chillerlan\QRCode\Common\{LuminanceSourceInterface, Version};
+use chillerlan\QRCode\Decoder\{Binarizer, BitMatrix};
+use function abs, intdiv, is_nan, max, min, round;
+use const NAN;
+
+/**
+ * Encapsulates logic that can detect a QR Code in an image, even if the QR Code
+ * is rotated or skewed, or partially obscured.
+ *
+ * @author Sean Owen
+ */
+final class Detector{
+
+ private BitMatrix $matrix;
+
+ /**
+ * Detector constructor.
+ */
+ public function __construct(LuminanceSourceInterface $source){
+ $this->matrix = (new Binarizer($source))->getBlackMatrix();
+ }
+
+ /**
+ * Detects a QR Code in an image.
+ */
+ public function detect():BitMatrix{
+ [$bottomLeft, $topLeft, $topRight] = (new FinderPatternFinder($this->matrix))->find();
+
+ $moduleSize = $this->calculateModuleSize($topLeft, $topRight, $bottomLeft);
+ $dimension = $this->computeDimension($topLeft, $topRight, $bottomLeft, $moduleSize);
+ $provisionalVersion = new Version(intdiv(($dimension - 17), 4));
+ $alignmentPattern = null;
+
+ // Anything above version 1 has an alignment pattern
+ if(!empty($provisionalVersion->getAlignmentPattern())){
+ // Guess where a "bottom right" finder pattern would have been
+ $bottomRightX = ($topRight->getX() - $topLeft->getX() + $bottomLeft->getX());
+ $bottomRightY = ($topRight->getY() - $topLeft->getY() + $bottomLeft->getY());
+
+ // Estimate that alignment pattern is closer by 3 modules
+ // from "bottom right" to known top left location
+ $correctionToTopLeft = (1.0 - 3.0 / (float)($provisionalVersion->getDimension() - 7));
+ $estAlignmentX = (int)($topLeft->getX() + $correctionToTopLeft * ($bottomRightX - $topLeft->getX()));
+ $estAlignmentY = (int)($topLeft->getY() + $correctionToTopLeft * ($bottomRightY - $topLeft->getY()));
+
+ // Kind of arbitrary -- expand search radius before giving up
+ for($i = 4; $i <= 16; $i <<= 1){//??????????
+ $alignmentPattern = $this->findAlignmentInRegion($moduleSize, $estAlignmentX, $estAlignmentY, (float)$i);
+
+ if($alignmentPattern !== null){
+ break;
+ }
+ }
+ // If we didn't find alignment pattern... well try anyway without it
+ }
+
+ $transform = $this->createTransform($topLeft, $topRight, $bottomLeft, $dimension, $alignmentPattern);
+
+ return (new GridSampler)->sampleGrid($this->matrix, $dimension, $transform);
+ }
+
+ /**
+ * Computes an average estimated module size based on estimated derived from the positions
+ * of the three finder patterns.
+ *
+ * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException
+ */
+ private function calculateModuleSize(FinderPattern $topLeft, FinderPattern $topRight, FinderPattern $bottomLeft):float{
+ // Take the average
+ $moduleSize = ((
+ $this->calculateModuleSizeOneWay($topLeft, $topRight) +
+ $this->calculateModuleSizeOneWay($topLeft, $bottomLeft)
+ ) / 2.0);
+
+ if($moduleSize < 1.0){
+ throw new QRCodeDetectorException('module size < 1.0');
+ }
+
+ return $moduleSize;
+ }
+
+ /**
+ * Estimates module size based on two finder patterns -- it uses
+ * #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int) to figure the
+ * width of each, measuring along the axis between their centers.
+ */
+ private function calculateModuleSizeOneWay(FinderPattern $a, FinderPattern $b):float{
+
+ $moduleSizeEst1 = $this->sizeOfBlackWhiteBlackRunBothWays($a->getX(), $a->getY(), $b->getX(), $b->getY());
+ $moduleSizeEst2 = $this->sizeOfBlackWhiteBlackRunBothWays($b->getX(), $b->getY(), $a->getX(), $a->getY());
+
+ if(is_nan($moduleSizeEst1)){
+ return ($moduleSizeEst2 / 7.0);
+ }
+
+ if(is_nan($moduleSizeEst2)){
+ return ($moduleSizeEst1 / 7.0);
+ }
+ // Average them, and divide by 7 since we've counted the width of 3 black modules,
+ // and 1 white and 1 black module on either side. Ergo, divide sum by 14.
+ return (($moduleSizeEst1 + $moduleSizeEst2) / 14.0);
+ }
+
+ /**
+ * See #sizeOfBlackWhiteBlackRun(int, int, int, int); computes the total width of
+ * a finder pattern by looking for a black-white-black run from the center in the direction
+ * of another po$(another finder pattern center), and in the opposite direction too.
+ *
+ * @noinspection DuplicatedCode
+ */
+ private function sizeOfBlackWhiteBlackRunBothWays(float $fromX, float $fromY, float $toX, float $toY):float{
+ $result = $this->sizeOfBlackWhiteBlackRun((int)$fromX, (int)$fromY, (int)$toX, (int)$toY);
+ $dimension = $this->matrix->getSize();
+ // Now count other way -- don't run off image though of course
+ $scale = 1.0;
+ $otherToX = ($fromX - ($toX - $fromX));
+
+ if($otherToX < 0){
+ $scale = ($fromX / ($fromX - $otherToX));
+ $otherToX = 0;
+ }
+ elseif($otherToX >= $dimension){
+ $scale = (($dimension - 1 - $fromX) / ($otherToX - $fromX));
+ $otherToX = ($dimension - 1);
+ }
+
+ $otherToY = (int)($fromY - ($toY - $fromY) * $scale);
+ $scale = 1.0;
+
+ if($otherToY < 0){
+ $scale = ($fromY / ($fromY - $otherToY));
+ $otherToY = 0;
+ }
+ elseif($otherToY >= $dimension){
+ $scale = (($dimension - 1 - $fromY) / ($otherToY - $fromY));
+ $otherToY = ($dimension - 1);
+ }
+
+ $otherToX = (int)($fromX + ($otherToX - $fromX) * $scale);
+ $result += $this->sizeOfBlackWhiteBlackRun((int)$fromX, (int)$fromY, $otherToX, $otherToY);
+
+ // Middle pixel is double-counted this way; subtract 1
+ return ($result - 1.0);
+ }
+
+ /**
+ * This method traces a line from a po$in the image, in the direction towards another point.
+ * It begins in a black region, and keeps going until it finds white, then black, then white again.
+ * It reports the distance from the start to this point.
+ *
+ * This is used when figuring out how wide a finder pattern is, when the finder pattern
+ * may be skewed or rotated.
+ */
+ private function sizeOfBlackWhiteBlackRun(int $fromX, int $fromY, int $toX, int $toY):float{
+ // Mild variant of Bresenham's algorithm;
+ // @see https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
+ $steep = abs($toY - $fromY) > abs($toX - $fromX);
+
+ if($steep){
+ $temp = $fromX;
+ $fromX = $fromY;
+ $fromY = $temp;
+ $temp = $toX;
+ $toX = $toY;
+ $toY = $temp;
+ }
+
+ $dx = abs($toX - $fromX);
+ $dy = abs($toY - $fromY);
+ $error = (-$dx / 2);
+ $xstep = (($fromX < $toX) ? 1 : -1);
+ $ystep = (($fromY < $toY) ? 1 : -1);
+
+ // In black pixels, looking for white, first or second time.
+ $state = 0;
+ // Loop up until x == toX, but not beyond
+ $xLimit = ($toX + $xstep);
+
+ for($x = $fromX, $y = $fromY; $x !== $xLimit; $x += $xstep){
+ $realX = ($steep) ? $y : $x;
+ $realY = ($steep) ? $x : $y;
+
+ // Does current pixel mean we have moved white to black or vice versa?
+ // Scanning black in state 0,2 and white in state 1, so if we find the wrong
+ // color, advance to next state or end if we are in state 2 already
+ if(($state === 1) === $this->matrix->check($realX, $realY)){
+
+ if($state === 2){
+ return FinderPattern::distance($x, $y, $fromX, $fromY);
+ }
+
+ $state++;
+ }
+
+ $error += $dy;
+
+ if($error > 0){
+
+ if($y === $toY){
+ break;
+ }
+
+ $y += $ystep;
+ $error -= $dx;
+ }
+ }
+
+ // Found black-white-black; give the benefit of the doubt that the next pixel outside the image
+ // is "white" so this last po$at (toX+xStep,toY) is the right ending. This is really a
+ // small approximation; (toX+xStep,toY+yStep) might be really correct. Ignore this.
+ if($state === 2){
+ return FinderPattern::distance(($toX + $xstep), $toY, $fromX, $fromY);
+ }
+
+ // else we didn't find even black-white-black; no estimate is really possible
+ return NAN;
+ }
+
+ /**
+ * Computes the dimension (number of modules on a size) of the QR Code based on the position
+ * of the finder patterns and estimated module size.
+ *
+ * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException
+ */
+ private function computeDimension(FinderPattern $nw, FinderPattern $ne, FinderPattern $sw, float $size):int{
+ $tltrCentersDimension = (int)round($nw->getDistance($ne) / $size);
+ $tlblCentersDimension = (int)round($nw->getDistance($sw) / $size);
+ $dimension = (int)((($tltrCentersDimension + $tlblCentersDimension) / 2) + 7);
+
+ switch($dimension % 4){
+ case 0:
+ $dimension++;
+ break;
+ // 1? do nothing
+ case 2:
+ $dimension--;
+ break;
+ case 3:
+ throw new QRCodeDetectorException('estimated dimension: '.$dimension);
+ }
+
+ if(($dimension % 4) !== 1){
+ throw new QRCodeDetectorException('dimension mod 4 is not 1');
+ }
+
+ return $dimension;
+ }
+
+ /**
+ * Attempts to locate an alignment pattern in a limited region of the image, which is
+ * guessed to contain it.
+ *
+ * @param float $overallEstModuleSize estimated module size so far
+ * @param int $estAlignmentX x coordinate of center of area probably containing alignment pattern
+ * @param int $estAlignmentY y coordinate of above
+ * @param float $allowanceFactor number of pixels in all directions to search from the center
+ *
+ * @return \chillerlan\QRCode\Detector\AlignmentPattern|null if found, or null otherwise
+ */
+ private function findAlignmentInRegion(
+ float $overallEstModuleSize,
+ int $estAlignmentX,
+ int $estAlignmentY,
+ float $allowanceFactor
+ ):?AlignmentPattern{
+ // Look for an alignment pattern (3 modules in size) around where it should be
+ $dimension = $this->matrix->getSize();
+ $allowance = (int)($allowanceFactor * $overallEstModuleSize);
+ $alignmentAreaLeftX = max(0, ($estAlignmentX - $allowance));
+ $alignmentAreaRightX = min(($dimension - 1), ($estAlignmentX + $allowance));
+
+ if(($alignmentAreaRightX - $alignmentAreaLeftX) < ($overallEstModuleSize * 3)){
+ return null;
+ }
+
+ $alignmentAreaTopY = max(0, ($estAlignmentY - $allowance));
+ $alignmentAreaBottomY = min(($dimension - 1), ($estAlignmentY + $allowance));
+
+ if(($alignmentAreaBottomY - $alignmentAreaTopY) < ($overallEstModuleSize * 3)){
+ return null;
+ }
+
+ return (new AlignmentPatternFinder($this->matrix, $overallEstModuleSize))->find(
+ $alignmentAreaLeftX,
+ $alignmentAreaTopY,
+ ($alignmentAreaRightX - $alignmentAreaLeftX),
+ ($alignmentAreaBottomY - $alignmentAreaTopY),
+ );
+ }
+
+ /**
+ *
+ */
+ private function createTransform(
+ FinderPattern $nw,
+ FinderPattern $ne,
+ FinderPattern $sw,
+ int $size,
+ ?AlignmentPattern $ap = null
+ ):PerspectiveTransform{
+ $dimMinusThree = ($size - 3.5);
+
+ if($ap instanceof AlignmentPattern){
+ $bottomRightX = $ap->getX();
+ $bottomRightY = $ap->getY();
+ $sourceBottomRightX = ($dimMinusThree - 3.0);
+ $sourceBottomRightY = $sourceBottomRightX;
+ }
+ else{
+ // Don't have an alignment pattern, just make up the bottom-right point
+ $bottomRightX = ($ne->getX() - $nw->getX() + $sw->getX());
+ $bottomRightY = ($ne->getY() - $nw->getY() + $sw->getY());
+ $sourceBottomRightX = $dimMinusThree;
+ $sourceBottomRightY = $dimMinusThree;
+ }
+
+ return (new PerspectiveTransform)->quadrilateralToQuadrilateral(
+ 3.5,
+ 3.5,
+ $dimMinusThree,
+ 3.5,
+ $sourceBottomRightX,
+ $sourceBottomRightY,
+ 3.5,
+ $dimMinusThree,
+ $nw->getX(),
+ $nw->getY(),
+ $ne->getX(),
+ $ne->getY(),
+ $bottomRightX,
+ $bottomRightY,
+ $sw->getX(),
+ $sw->getY()
+ );
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php b/vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php
new file mode 100644
index 000000000..3ae4650ad
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * Class FinderPattern
+ *
+ * @created 17.01.2021
+ * @author ZXing Authors
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license Apache-2.0
+ */
+
+namespace chillerlan\QRCode\Detector;
+
+use function sqrt;
+
+/**
+ * Encapsulates a finder pattern, which are the three square patterns found in
+ * the corners of QR Codes. It also encapsulates a count of similar finder patterns,
+ * as a convenience to the finder's bookkeeping.
+ *
+ * @author Sean Owen
+ */
+final class FinderPattern extends ResultPoint{
+
+ private int $count;
+
+ /**
+ *
+ */
+ public function __construct(float $posX, float $posY, float $estimatedModuleSize, ?int $count = null){
+ parent::__construct($posX, $posY, $estimatedModuleSize);
+
+ $this->count = ($count ?? 1);
+ }
+
+ /**
+ *
+ */
+ public function getCount():int{
+ return $this->count;
+ }
+
+ /**
+ * @param \chillerlan\QRCode\Detector\FinderPattern $b second pattern
+ *
+ * @return float distance between two points
+ */
+ public function getDistance(FinderPattern $b):float{
+ return self::distance($this->x, $this->y, $b->x, $b->y);
+ }
+
+ /**
+ * Get square of distance between a and b.
+ */
+ public function getSquaredDistance(FinderPattern $b):float{
+ return self::squaredDistance($this->x, $this->y, $b->x, $b->y);
+ }
+
+ /**
+ * Combines this object's current estimate of a finder pattern position and module size
+ * with a new estimate. It returns a new FinderPattern containing a weighted average
+ * based on count.
+ */
+ public function combineEstimate(float $i, float $j, float $newModuleSize):self{
+ $combinedCount = ($this->count + 1);
+
+ return new self(
+ ($this->count * $this->x + $j) / $combinedCount,
+ ($this->count * $this->y + $i) / $combinedCount,
+ ($this->count * $this->estimatedModuleSize + $newModuleSize) / $combinedCount,
+ $combinedCount
+ );
+ }
+
+ /**
+ *
+ */
+ private static function squaredDistance(float $aX, float $aY, float $bX, float $bY):float{
+ $xDiff = ($aX - $bX);
+ $yDiff = ($aY - $bY);
+
+ return ($xDiff * $xDiff + $yDiff * $yDiff);
+ }
+
+ /**
+ *
+ */
+ public static function distance(float $aX, float $aY, float $bX, float $bY):float{
+ return sqrt(self::squaredDistance($aX, $aY, $bX, $bY));
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php b/vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php
new file mode 100644
index 000000000..61628d063
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php
@@ -0,0 +1,773 @@
+<?php
+/**
+ * Class FinderPatternFinder
+ *
+ * @created 17.01.2021
+ * @author ZXing Authors
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license Apache-2.0
+ *
+ * @phan-file-suppress PhanTypePossiblyInvalidDimOffset
+ */
+
+namespace chillerlan\QRCode\Detector;
+
+use chillerlan\QRCode\Decoder\BitMatrix;
+use function abs, count, intdiv, usort;
+use const PHP_FLOAT_MAX;
+
+/**
+ * This class attempts to find finder patterns in a QR Code. Finder patterns are the square
+ * markers at three corners of a QR Code.
+ *
+ * This class is thread-safe but not reentrant. Each thread must allocate its own object.
+ *
+ * @author Sean Owen
+ */
+final class FinderPatternFinder{
+
+ private const MIN_SKIP = 2;
+ private const MAX_MODULES = 177; // 1 pixel/module times 3 modules/center
+ private const CENTER_QUORUM = 2; // support up to version 10 for mobile clients
+ private BitMatrix $matrix;
+ /** @var \chillerlan\QRCode\Detector\FinderPattern[] */
+ private array $possibleCenters;
+ private bool $hasSkipped = false;
+
+ /**
+ * Creates a finder that will search the image for three finder patterns.
+ *
+ * @param BitMatrix $matrix image to search
+ */
+ public function __construct(BitMatrix $matrix){
+ $this->matrix = $matrix;
+ $this->possibleCenters = [];
+ }
+
+ /**
+ * @return \chillerlan\QRCode\Detector\FinderPattern[]
+ */
+ public function find():array{
+ $dimension = $this->matrix->getSize();
+
+ // We are looking for black/white/black/white/black modules in
+ // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far
+ // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the
+ // image, and then account for the center being 3 modules in size. This gives the smallest
+ // number of pixels the center could be, so skip this often.
+ $iSkip = intdiv((3 * $dimension), (4 * self::MAX_MODULES));
+
+ if($iSkip < self::MIN_SKIP){
+ $iSkip = self::MIN_SKIP;
+ }
+
+ $done = false;
+
+ for($i = ($iSkip - 1); ($i < $dimension) && !$done; $i += $iSkip){
+ // Get a row of black/white values
+ $stateCount = $this->getCrossCheckStateCount();
+ $currentState = 0;
+
+ for($j = 0; $j < $dimension; $j++){
+
+ // Black pixel
+ if($this->matrix->check($j, $i)){
+ // Counting white pixels
+ if(($currentState & 1) === 1){
+ $currentState++;
+ }
+
+ $stateCount[$currentState]++;
+ }
+ // White pixel
+ else{
+ // Counting black pixels
+ if(($currentState & 1) === 0){
+ // A winner?
+ if($currentState === 4){
+ // Yes
+ if($this->foundPatternCross($stateCount)){
+ $confirmed = $this->handlePossibleCenter($stateCount, $i, $j);
+
+ if($confirmed){
+ // Start examining every other line. Checking each line turned out to be too
+ // expensive and didn't improve performance.
+ $iSkip = 3;
+
+ if($this->hasSkipped){
+ $done = $this->haveMultiplyConfirmedCenters();
+ }
+ else{
+ $rowSkip = $this->findRowSkip();
+
+ if($rowSkip > $stateCount[2]){
+ // Skip rows between row of lower confirmed center
+ // and top of presumed third confirmed center
+ // but back up a bit to get a full chance of detecting
+ // it, entire width of center of finder pattern
+
+ // Skip by rowSkip, but back off by $stateCount[2] (size of last center
+ // of pattern we saw) to be conservative, and also back off by iSkip which
+ // is about to be re-added
+ $i += ($rowSkip - $stateCount[2] - $iSkip);
+ $j = ($dimension - 1);
+ }
+ }
+ }
+ else{
+ $stateCount = $this->doShiftCounts2($stateCount);
+ $currentState = 3;
+
+ continue;
+ }
+ // Clear state to start looking again
+ $currentState = 0;
+ $stateCount = $this->getCrossCheckStateCount();
+ }
+ // No, shift counts back by two
+ else{
+ $stateCount = $this->doShiftCounts2($stateCount);
+ $currentState = 3;
+ }
+ }
+ else{
+ $stateCount[++$currentState]++;
+ }
+ }
+ // Counting white pixels
+ else{
+ $stateCount[$currentState]++;
+ }
+ }
+ }
+
+ if($this->foundPatternCross($stateCount)){
+ $confirmed = $this->handlePossibleCenter($stateCount, $i, $dimension);
+
+ if($confirmed){
+ $iSkip = $stateCount[0];
+
+ if($this->hasSkipped){
+ // Found a third one
+ $done = $this->haveMultiplyConfirmedCenters();
+ }
+ }
+ }
+ }
+
+ return $this->orderBestPatterns($this->selectBestPatterns());
+ }
+
+ /**
+ * @return int[]
+ */
+ private function getCrossCheckStateCount():array{
+ return [0, 0, 0, 0, 0];
+ }
+
+ /**
+ * @param int[] $stateCount
+ *
+ * @return int[]
+ */
+ private function doShiftCounts2(array $stateCount):array{
+ $stateCount[0] = $stateCount[2];
+ $stateCount[1] = $stateCount[3];
+ $stateCount[2] = $stateCount[4];
+ $stateCount[3] = 1;
+ $stateCount[4] = 0;
+
+ return $stateCount;
+ }
+
+ /**
+ * Given a count of black/white/black/white/black pixels just seen and an end position,
+ * figures the location of the center of this run.
+ *
+ * @param int[] $stateCount
+ */
+ private function centerFromEnd(array $stateCount, int $end):float{
+ return (float)(($end - $stateCount[4] - $stateCount[3]) - $stateCount[2] / 2);
+ }
+
+ /**
+ * @param int[] $stateCount
+ */
+ private function foundPatternCross(array $stateCount):bool{
+ // Allow less than 50% variance from 1-1-3-1-1 proportions
+ return $this->foundPatternVariance($stateCount, 2.0);
+ }
+
+ /**
+ * @param int[] $stateCount
+ */
+ private function foundPatternDiagonal(array $stateCount):bool{
+ // Allow less than 75% variance from 1-1-3-1-1 proportions
+ return $this->foundPatternVariance($stateCount, 1.333);
+ }
+
+ /**
+ * @param int[] $stateCount count of black/white/black/white/black pixels just read
+ *
+ * @return bool true if the proportions of the counts is close enough to the 1/1/3/1/1 ratios
+ * used by finder patterns to be considered a match
+ */
+ private function foundPatternVariance(array $stateCount, float $variance):bool{
+ $totalModuleSize = 0;
+
+ for($i = 0; $i < 5; $i++){
+ $count = $stateCount[$i];
+
+ if($count === 0){
+ return false;
+ }
+
+ $totalModuleSize += $count;
+ }
+
+ if($totalModuleSize < 7){
+ return false;
+ }
+
+ $moduleSize = ($totalModuleSize / 7.0);
+ $maxVariance = ($moduleSize / $variance);
+
+ return
+ abs($moduleSize - $stateCount[0]) < $maxVariance
+ && abs($moduleSize - $stateCount[1]) < $maxVariance
+ && abs(3.0 * $moduleSize - $stateCount[2]) < (3 * $maxVariance)
+ && abs($moduleSize - $stateCount[3]) < $maxVariance
+ && abs($moduleSize - $stateCount[4]) < $maxVariance;
+ }
+
+ /**
+ * After a vertical and horizontal scan finds a potential finder pattern, this method
+ * "cross-cross-cross-checks" by scanning down diagonally through the center of the possible
+ * finder pattern to see if the same proportion is detected.
+ *
+ * @param int $centerI row where a finder pattern was detected
+ * @param int $centerJ center of the section that appears to cross a finder pattern
+ *
+ * @return bool true if proportions are withing expected limits
+ */
+ private function crossCheckDiagonal(int $centerI, int $centerJ):bool{
+ $stateCount = $this->getCrossCheckStateCount();
+
+ // Start counting up, left from center finding black center mass
+ $i = 0;
+
+ while($centerI >= $i && $centerJ >= $i && $this->matrix->check(($centerJ - $i), ($centerI - $i))){
+ $stateCount[2]++;
+ $i++;
+ }
+
+ if($stateCount[2] === 0){
+ return false;
+ }
+
+ // Continue up, left finding white space
+ while($centerI >= $i && $centerJ >= $i && !$this->matrix->check(($centerJ - $i), ($centerI - $i))){
+ $stateCount[1]++;
+ $i++;
+ }
+
+ if($stateCount[1] === 0){
+ return false;
+ }
+
+ // Continue up, left finding black border
+ while($centerI >= $i && $centerJ >= $i && $this->matrix->check(($centerJ - $i), ($centerI - $i))){
+ $stateCount[0]++;
+ $i++;
+ }
+
+ if($stateCount[0] === 0){
+ return false;
+ }
+
+ $dimension = $this->matrix->getSize();
+
+ // Now also count down, right from center
+ $i = 1;
+ // phpcs:ignore
+ while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && $this->matrix->check(($centerJ + $i), ($centerI + $i))){
+ $stateCount[2]++;
+ $i++;
+ }
+
+ // phpcs:ignore
+ while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && !$this->matrix->check(($centerJ + $i), ($centerI + $i))){
+ $stateCount[3]++;
+ $i++;
+ }
+
+ if($stateCount[3] === 0){
+ return false;
+ }
+
+ // phpcs:ignore
+ while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && $this->matrix->check(($centerJ + $i), ($centerI + $i))){
+ $stateCount[4]++;
+ $i++;
+ }
+
+ if($stateCount[4] === 0){
+ return false;
+ }
+
+ return $this->foundPatternDiagonal($stateCount);
+ }
+
+ /**
+ * After a horizontal scan finds a potential finder pattern, this method
+ * "cross-checks" by scanning down vertically through the center of the possible
+ * finder pattern to see if the same proportion is detected.
+ *
+ * @param int $startI row where a finder pattern was detected
+ * @param int $centerJ center of the section that appears to cross a finder pattern
+ * @param int $maxCount maximum reasonable number of modules that should be
+ * observed in any reading state, based on the results of the horizontal scan
+ * @param int $originalStateCountTotal
+ *
+ * @return float|null vertical center of finder pattern, or null if not found
+ * @noinspection DuplicatedCode
+ */
+ private function crossCheckVertical(int $startI, int $centerJ, int $maxCount, int $originalStateCountTotal):?float{
+ $maxI = $this->matrix->getSize();
+ $stateCount = $this->getCrossCheckStateCount();
+
+ // Start counting up from center
+ $i = $startI;
+ while($i >= 0 && $this->matrix->check($centerJ, $i)){
+ $stateCount[2]++;
+ $i--;
+ }
+
+ if($i < 0){
+ return null;
+ }
+
+ while($i >= 0 && !$this->matrix->check($centerJ, $i) && $stateCount[1] <= $maxCount){
+ $stateCount[1]++;
+ $i--;
+ }
+
+ // If already too many modules in this state or ran off the edge:
+ if($i < 0 || $stateCount[1] > $maxCount){
+ return null;
+ }
+
+ while($i >= 0 && $this->matrix->check($centerJ, $i) && $stateCount[0] <= $maxCount){
+ $stateCount[0]++;
+ $i--;
+ }
+
+ if($stateCount[0] > $maxCount){
+ return null;
+ }
+
+ // Now also count down from center
+ $i = ($startI + 1);
+ while($i < $maxI && $this->matrix->check($centerJ, $i)){
+ $stateCount[2]++;
+ $i++;
+ }
+
+ if($i === $maxI){
+ return null;
+ }
+
+ while($i < $maxI && !$this->matrix->check($centerJ, $i) && $stateCount[3] < $maxCount){
+ $stateCount[3]++;
+ $i++;
+ }
+
+ if($i === $maxI || $stateCount[3] >= $maxCount){
+ return null;
+ }
+
+ while($i < $maxI && $this->matrix->check($centerJ, $i) && $stateCount[4] < $maxCount){
+ $stateCount[4]++;
+ $i++;
+ }
+
+ if($stateCount[4] >= $maxCount){
+ return null;
+ }
+
+ // If we found a finder-pattern-like section, but its size is more than 40% different from
+ // the original, assume it's a false positive
+ $stateCountTotal = ($stateCount[0] + $stateCount[1] + $stateCount[2] + $stateCount[3] + $stateCount[4]);
+
+ if((5 * abs($stateCountTotal - $originalStateCountTotal)) >= (2 * $originalStateCountTotal)){
+ return null;
+ }
+
+ if(!$this->foundPatternCross($stateCount)){
+ return null;
+ }
+
+ return $this->centerFromEnd($stateCount, $i);
+ }
+
+ /**
+ * Like #crossCheckVertical(int, int, int, int), and in fact is basically identical,
+ * except it reads horizontally instead of vertically. This is used to cross-cross
+ * check a vertical cross-check and locate the real center of the alignment pattern.
+ * @noinspection DuplicatedCode
+ */
+ private function crossCheckHorizontal(int $startJ, int $centerI, int $maxCount, int $originalStateCountTotal):?float{
+ $maxJ = $this->matrix->getSize();
+ $stateCount = $this->getCrossCheckStateCount();
+
+ $j = $startJ;
+ while($j >= 0 && $this->matrix->check($j, $centerI)){
+ $stateCount[2]++;
+ $j--;
+ }
+
+ if($j < 0){
+ return null;
+ }
+
+ while($j >= 0 && !$this->matrix->check($j, $centerI) && $stateCount[1] <= $maxCount){
+ $stateCount[1]++;
+ $j--;
+ }
+
+ if($j < 0 || $stateCount[1] > $maxCount){
+ return null;
+ }
+
+ while($j >= 0 && $this->matrix->check($j, $centerI) && $stateCount[0] <= $maxCount){
+ $stateCount[0]++;
+ $j--;
+ }
+
+ if($stateCount[0] > $maxCount){
+ return null;
+ }
+
+ $j = ($startJ + 1);
+ while($j < $maxJ && $this->matrix->check($j, $centerI)){
+ $stateCount[2]++;
+ $j++;
+ }
+
+ if($j === $maxJ){
+ return null;
+ }
+
+ while($j < $maxJ && !$this->matrix->check($j, $centerI) && $stateCount[3] < $maxCount){
+ $stateCount[3]++;
+ $j++;
+ }
+
+ if($j === $maxJ || $stateCount[3] >= $maxCount){
+ return null;
+ }
+
+ while($j < $maxJ && $this->matrix->check($j, $centerI) && $stateCount[4] < $maxCount){
+ $stateCount[4]++;
+ $j++;
+ }
+
+ if($stateCount[4] >= $maxCount){
+ return null;
+ }
+
+ // If we found a finder-pattern-like section, but its size is significantly different from
+ // the original, assume it's a false positive
+ $stateCountTotal = ($stateCount[0] + $stateCount[1] + $stateCount[2] + $stateCount[3] + $stateCount[4]);
+
+ if((5 * abs($stateCountTotal - $originalStateCountTotal)) >= $originalStateCountTotal){
+ return null;
+ }
+
+ if(!$this->foundPatternCross($stateCount)){
+ return null;
+ }
+
+ return $this->centerFromEnd($stateCount, $j);
+ }
+
+ /**
+ * This is called when a horizontal scan finds a possible alignment pattern. It will
+ * cross-check with a vertical scan, and if successful, will, ah, cross-cross-check
+ * with another horizontal scan. This is needed primarily to locate the real horizontal
+ * center of the pattern in cases of extreme skew.
+ * And then we cross-cross-cross check with another diagonal scan.
+ *
+ * If that succeeds the finder pattern location is added to a list that tracks
+ * the number of times each location has been nearly-matched as a finder pattern.
+ * Each additional find is more evidence that the location is in fact a finder
+ * pattern center
+ *
+ * @param int[] $stateCount reading state module counts from horizontal scan
+ * @param int $i row where finder pattern may be found
+ * @param int $j end of possible finder pattern in row
+ *
+ * @return bool if a finder pattern candidate was found this time
+ */
+ private function handlePossibleCenter(array $stateCount, int $i, int $j):bool{
+ $stateCountTotal = ($stateCount[0] + $stateCount[1] + $stateCount[2] + $stateCount[3] + $stateCount[4]);
+ $centerJ = $this->centerFromEnd($stateCount, $j);
+ $centerI = $this->crossCheckVertical($i, (int)$centerJ, $stateCount[2], $stateCountTotal);
+
+ if($centerI !== null){
+ // Re-cross check
+ $centerJ = $this->crossCheckHorizontal((int)$centerJ, (int)$centerI, $stateCount[2], $stateCountTotal);
+ if($centerJ !== null && ($this->crossCheckDiagonal((int)$centerI, (int)$centerJ))){
+ $estimatedModuleSize = ($stateCountTotal / 7.0);
+ $found = false;
+
+ // cautious (was in for fool in which $this->possibleCenters is updated)
+ $count = count($this->possibleCenters);
+
+ for($index = 0; $index < $count; $index++){
+ $center = $this->possibleCenters[$index];
+ // Look for about the same center and module size:
+ if($center->aboutEquals($estimatedModuleSize, $centerI, $centerJ)){
+ $this->possibleCenters[$index] = $center->combineEstimate($centerI, $centerJ, $estimatedModuleSize);
+ $found = true;
+ break;
+ }
+ }
+
+ if(!$found){
+ $point = new FinderPattern($centerJ, $centerI, $estimatedModuleSize);
+ $this->possibleCenters[] = $point;
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @return int number of rows we could safely skip during scanning, based on the first
+ * two finder patterns that have been located. In some cases their position will
+ * allow us to infer that the third pattern must lie below a certain point farther
+ * down in the image.
+ */
+ private function findRowSkip():int{
+ $max = count($this->possibleCenters);
+
+ if($max <= 1){
+ return 0;
+ }
+
+ $firstConfirmedCenter = null;
+
+ foreach($this->possibleCenters as $center){
+
+ if($center->getCount() >= self::CENTER_QUORUM){
+
+ if($firstConfirmedCenter === null){
+ $firstConfirmedCenter = $center;
+ }
+ else{
+ // We have two confirmed centers
+ // How far down can we skip before resuming looking for the next
+ // pattern? In the worst case, only the difference between the
+ // difference in the x / y coordinates of the two centers.
+ // This is the case where you find top left last.
+ $this->hasSkipped = true;
+
+ return (int)((abs($firstConfirmedCenter->getX() - $center->getX()) -
+ abs($firstConfirmedCenter->getY() - $center->getY())) / 2);
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * @return bool true if we have found at least 3 finder patterns that have been detected
+ * at least #CENTER_QUORUM times each, and, the estimated module size of the
+ * candidates is "pretty similar"
+ */
+ private function haveMultiplyConfirmedCenters():bool{
+ $confirmedCount = 0;
+ $totalModuleSize = 0.0;
+ $max = count($this->possibleCenters);
+
+ foreach($this->possibleCenters as $pattern){
+ if($pattern->getCount() >= self::CENTER_QUORUM){
+ $confirmedCount++;
+ $totalModuleSize += $pattern->getEstimatedModuleSize();
+ }
+ }
+
+ if($confirmedCount < 3){
+ return false;
+ }
+ // OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive"
+ // and that we need to keep looking. We detect this by asking if the estimated module sizes
+ // vary too much. We arbitrarily say that when the total deviation from average exceeds
+ // 5% of the total module size estimates, it's too much.
+ $average = ($totalModuleSize / (float)$max);
+ $totalDeviation = 0.0;
+
+ foreach($this->possibleCenters as $pattern){
+ $totalDeviation += abs($pattern->getEstimatedModuleSize() - $average);
+ }
+
+ return $totalDeviation <= (0.05 * $totalModuleSize);
+ }
+
+ /**
+ * @return \chillerlan\QRCode\Detector\FinderPattern[] the 3 best FinderPatterns from our list of candidates. The "best" are
+ * those that have been detected at least #CENTER_QUORUM times, and whose module
+ * size differs from the average among those patterns the least
+ * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException if 3 such finder patterns do not exist
+ */
+ private function selectBestPatterns():array{
+ $startSize = count($this->possibleCenters);
+
+ if($startSize < 3){
+ throw new QRCodeDetectorException('could not find enough finder patterns');
+ }
+
+ usort(
+ $this->possibleCenters,
+ fn(FinderPattern $a, FinderPattern $b) => ($a->getEstimatedModuleSize() <=> $b->getEstimatedModuleSize())
+ );
+
+ $distortion = PHP_FLOAT_MAX;
+ $bestPatterns = [];
+
+ for($i = 0; $i < ($startSize - 2); $i++){
+ $fpi = $this->possibleCenters[$i];
+ $minModuleSize = $fpi->getEstimatedModuleSize();
+
+ for($j = ($i + 1); $j < ($startSize - 1); $j++){
+ $fpj = $this->possibleCenters[$j];
+ $squares0 = $fpi->getSquaredDistance($fpj);
+
+ for($k = ($j + 1); $k < $startSize; $k++){
+ $fpk = $this->possibleCenters[$k];
+ $maxModuleSize = $fpk->getEstimatedModuleSize();
+
+ // module size is not similar
+ if($maxModuleSize > ($minModuleSize * 1.4)){
+ continue;
+ }
+
+ $a = $squares0;
+ $b = $fpj->getSquaredDistance($fpk);
+ $c = $fpi->getSquaredDistance($fpk);
+
+ // sorts ascending - inlined
+ if($a < $b){
+ if($b > $c){
+ if($a < $c){
+ $temp = $b;
+ $b = $c;
+ $c = $temp;
+ }
+ else{
+ $temp = $a;
+ $a = $c;
+ $c = $b;
+ $b = $temp;
+ }
+ }
+ }
+ else{
+ if($b < $c){
+ if($a < $c){
+ $temp = $a;
+ $a = $b;
+ $b = $temp;
+ }
+ else{
+ $temp = $a;
+ $a = $b;
+ $b = $c;
+ $c = $temp;
+ }
+ }
+ else{
+ $temp = $a;
+ $a = $c;
+ $c = $temp;
+ }
+ }
+
+ // a^2 + b^2 = c^2 (Pythagorean theorem), and a = b (isosceles triangle).
+ // Since any right triangle satisfies the formula c^2 - b^2 - a^2 = 0,
+ // we need to check both two equal sides separately.
+ // The value of |c^2 - 2 * b^2| + |c^2 - 2 * a^2| increases as dissimilarity
+ // from isosceles right triangle.
+ $d = (abs($c - 2 * $b) + abs($c - 2 * $a));
+
+ if($d < $distortion){
+ $distortion = $d;
+ $bestPatterns = [$fpi, $fpj, $fpk];
+ }
+ }
+ }
+ }
+
+ if($distortion === PHP_FLOAT_MAX){
+ throw new QRCodeDetectorException('finder patterns may be too distorted');
+ }
+
+ return $bestPatterns;
+ }
+
+ /**
+ * Orders an array of three ResultPoints in an order [A,B,C] such that AB is less than AC
+ * and BC is less than AC, and the angle between BC and BA is less than 180 degrees.
+ *
+ * @param \chillerlan\QRCode\Detector\FinderPattern[] $patterns array of three FinderPattern to order
+ *
+ * @return \chillerlan\QRCode\Detector\FinderPattern[]
+ */
+ private function orderBestPatterns(array $patterns):array{
+
+ // Find distances between pattern centers
+ $zeroOneDistance = $patterns[0]->getDistance($patterns[1]);
+ $oneTwoDistance = $patterns[1]->getDistance($patterns[2]);
+ $zeroTwoDistance = $patterns[0]->getDistance($patterns[2]);
+
+ // Assume one closest to other two is B; A and C will just be guesses at first
+ if($oneTwoDistance >= $zeroOneDistance && $oneTwoDistance >= $zeroTwoDistance){
+ [$pointB, $pointA, $pointC] = $patterns;
+ }
+ elseif($zeroTwoDistance >= $oneTwoDistance && $zeroTwoDistance >= $zeroOneDistance){
+ [$pointA, $pointB, $pointC] = $patterns;
+ }
+ else{
+ [$pointA, $pointC, $pointB] = $patterns;
+ }
+
+ // Use cross product to figure out whether A and C are correct or flipped.
+ // This asks whether BC x BA has a positive z component, which is the arrangement
+ // we want for A, B, C. If it's negative, then we've got it flipped around and
+ // should swap A and C.
+ if($this->crossProductZ($pointA, $pointB, $pointC) < 0.0){
+ $temp = $pointA;
+ $pointA = $pointC;
+ $pointC = $temp;
+ }
+
+ return [$pointA, $pointB, $pointC];
+ }
+
+ /**
+ * Returns the z component of the cross product between vectors BC and BA.
+ */
+ private function crossProductZ(FinderPattern $pointA, FinderPattern $pointB, FinderPattern $pointC):float{
+ $bX = $pointB->getX();
+ $bY = $pointB->getY();
+
+ return ((($pointC->getX() - $bX) * ($pointA->getY() - $bY)) - (($pointC->getY() - $bY) * ($pointA->getX() - $bX)));
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php b/vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php
new file mode 100644
index 000000000..f70bb0eca
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php
@@ -0,0 +1,181 @@
+<?php
+/**
+ * Class GridSampler
+ *
+ * @created 17.01.2021
+ * @author ZXing Authors
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license Apache-2.0
+ */
+
+namespace chillerlan\QRCode\Detector;
+
+use chillerlan\QRCode\Data\QRMatrix;
+use chillerlan\QRCode\Decoder\BitMatrix;
+use function array_fill, count, intdiv, sprintf;
+
+/**
+ * Implementations of this class can, given locations of finder patterns for a QR code in an
+ * image, sample the right points in the image to reconstruct the QR code, accounting for
+ * perspective distortion. It is abstracted since it is relatively expensive and should be allowed
+ * to take advantage of platform-specific optimized implementations, like Sun's Java Advanced
+ * Imaging library, but which may not be available in other environments such as J2ME, and vice
+ * versa.
+ *
+ * The implementation used can be controlled by calling #setGridSampler(GridSampler)
+ * with an instance of a class which implements this interface.
+ *
+ * @author Sean Owen
+ */
+final class GridSampler{
+
+ private array $points;
+
+ /**
+ * Checks a set of points that have been transformed to sample points on an image against
+ * the image's dimensions to see if the point are even within the image.
+ *
+ * This method will actually "nudge" the endpoints back onto the image if they are found to be
+ * barely (less than 1 pixel) off the image. This accounts for imperfect detection of finder
+ * patterns in an image where the QR Code runs all the way to the image border.
+ *
+ * For efficiency, the method will check points from either end of the line until one is found
+ * to be within the image. Because the set of points are assumed to be linear, this is valid.
+ *
+ * @param int $dimension matrix width/height
+ *
+ * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException if an endpoint is lies outside the image boundaries
+ */
+ private function checkAndNudgePoints(int $dimension):void{
+ $nudged = true;
+ $max = count($this->points);
+
+ // Check and nudge points from start until we see some that are OK:
+ for($offset = 0; $offset < $max && $nudged; $offset += 2){
+ $x = (int)$this->points[$offset];
+ $y = (int)$this->points[($offset + 1)];
+
+ if($x < -1 || $x > $dimension || $y < -1 || $y > $dimension){
+ throw new QRCodeDetectorException(sprintf('checkAndNudgePoints 1, x: %s, y: %s, d: %s', $x, $y, $dimension));
+ }
+
+ $nudged = false;
+
+ if($x === -1){
+ $this->points[$offset] = 0.0;
+ $nudged = true;
+ }
+ elseif($x === $dimension){
+ $this->points[$offset] = ($dimension - 1);
+ $nudged = true;
+ }
+
+ if($y === -1){
+ $this->points[($offset + 1)] = 0.0;
+ $nudged = true;
+ }
+ elseif($y === $dimension){
+ $this->points[($offset + 1)] = ($dimension - 1);
+ $nudged = true;
+ }
+
+ }
+
+ // Check and nudge points from end:
+ $nudged = true;
+
+ for($offset = ($max - 2); $offset >= 0 && $nudged; $offset -= 2){
+ $x = (int)$this->points[$offset];
+ $y = (int)$this->points[($offset + 1)];
+
+ if($x < -1 || $x > $dimension || $y < -1 || $y > $dimension){
+ throw new QRCodeDetectorException(sprintf('checkAndNudgePoints 2, x: %s, y: %s, d: %s', $x, $y, $dimension));
+ }
+
+ $nudged = false;
+
+ if($x === -1){
+ $this->points[$offset] = 0.0;
+ $nudged = true;
+ }
+ elseif($x === $dimension){
+ $this->points[$offset] = ($dimension - 1);
+ $nudged = true;
+ }
+
+ if($y === -1){
+ $this->points[($offset + 1)] = 0.0;
+ $nudged = true;
+ }
+ elseif($y === $dimension){
+ $this->points[($offset + 1)] = ($dimension - 1);
+ $nudged = true;
+ }
+
+ }
+
+ }
+
+ /**
+ * Samples an image for a rectangular matrix of bits of the given dimension. The sampling
+ * transformation is determined by the coordinates of 4 points, in the original and transformed
+ * image space.
+ *
+ * @return \chillerlan\QRCode\Decoder\BitMatrix representing a grid of points sampled from the image within a region
+ * defined by the "from" parameters
+ * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException if image can't be sampled, for example, if the transformation defined
+ * by the given points is invalid or results in sampling outside the image boundaries
+ */
+ public function sampleGrid(BitMatrix $matrix, int $dimension, PerspectiveTransform $transform):BitMatrix{
+
+ if($dimension <= 0){
+ throw new QRCodeDetectorException('invalid matrix size');
+ }
+
+ $bits = new BitMatrix($dimension);
+ $this->points = array_fill(0, (2 * $dimension), 0.0);
+
+ for($y = 0; $y < $dimension; $y++){
+ $max = count($this->points);
+ $iValue = ($y + 0.5);
+
+ for($x = 0; $x < $max; $x += 2){
+ $this->points[$x] = (($x / 2) + 0.5);
+ $this->points[($x + 1)] = $iValue;
+ }
+ // phpcs:ignore
+ [$this->points, ] = $transform->transformPoints($this->points);
+ // Quick check to see if points transformed to something inside the image;
+ // sufficient to check the endpoints
+ $this->checkAndNudgePoints($matrix->getSize());
+
+ // no need to try/catch as QRMatrix::set() will silently discard out of bounds values
+# try{
+ for($x = 0; $x < $max; $x += 2){
+ // Black(-ish) pixel
+ $bits->set(
+ intdiv($x, 2),
+ $y,
+ $matrix->check((int)$this->points[$x], (int)$this->points[($x + 1)]),
+ QRMatrix::M_DATA
+ );
+ }
+# }
+# catch(\Throwable $aioobe){//ArrayIndexOutOfBoundsException
+ // This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting
+ // transform gets "twisted" such that it maps a straight line of points to a set of points
+ // whose endpoints are in bounds, but others are not. There is probably some mathematical
+ // way to detect this about the transformation that I don't know yet.
+ // This results in an ugly runtime exception despite our clever checks above -- can't have
+ // that. We could check each point's coordinates but that feels duplicative. We settle for
+ // catching and wrapping ArrayIndexOutOfBoundsException.
+# throw new QRCodeDetectorException('ArrayIndexOutOfBoundsException');
+# }
+
+ }
+
+ return $bits;
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php b/vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php
new file mode 100644
index 000000000..7964092e3
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php
@@ -0,0 +1,182 @@
+<?php
+/**
+ * Class PerspectiveTransform
+ *
+ * @created 17.01.2021
+ * @author ZXing Authors
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license Apache-2.0
+ */
+
+namespace chillerlan\QRCode\Detector;
+
+use function count;
+
+/**
+ * This class implements a perspective transform in two dimensions. Given four source and four
+ * destination points, it will compute the transformation implied between them. The code is based
+ * directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.
+ *
+ * @author Sean Owen
+ */
+final class PerspectiveTransform{
+
+ private float $a11;
+ private float $a12;
+ private float $a13;
+ private float $a21;
+ private float $a22;
+ private float $a23;
+ private float $a31;
+ private float $a32;
+ private float $a33;
+
+ /**
+ *
+ */
+ private function set(
+ float $a11, float $a21, float $a31,
+ float $a12, float $a22, float $a32,
+ float $a13, float $a23, float $a33
+ ):self{
+ $this->a11 = $a11;
+ $this->a12 = $a12;
+ $this->a13 = $a13;
+ $this->a21 = $a21;
+ $this->a22 = $a22;
+ $this->a23 = $a23;
+ $this->a31 = $a31;
+ $this->a32 = $a32;
+ $this->a33 = $a33;
+
+ return $this;
+ }
+
+ /**
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
+ */
+ public function quadrilateralToQuadrilateral(
+ float $x0, float $y0, float $x1, float $y1, float $x2, float $y2, float $x3, float $y3,
+ float $x0p, float $y0p, float $x1p, float $y1p, float $x2p, float $y2p, float $x3p, float $y3p
+ ):self{
+ return (new self)
+ ->squareToQuadrilateral($x0p, $y0p, $x1p, $y1p, $x2p, $y2p, $x3p, $y3p)
+ ->times($this->quadrilateralToSquare($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3));
+ }
+
+ /**
+ *
+ */
+ private function quadrilateralToSquare(
+ float $x0, float $y0, float $x1, float $y1,
+ float $x2, float $y2, float $x3, float $y3
+ ):self{
+ // Here, the adjoint serves as the inverse:
+ return $this
+ ->squareToQuadrilateral($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
+ ->buildAdjoint();
+ }
+
+ /**
+ *
+ */
+ private function buildAdjoint():self{
+ // Adjoint is the transpose of the cofactor matrix:
+ return $this->set(
+ ($this->a22 * $this->a33 - $this->a23 * $this->a32),
+ ($this->a23 * $this->a31 - $this->a21 * $this->a33),
+ ($this->a21 * $this->a32 - $this->a22 * $this->a31),
+ ($this->a13 * $this->a32 - $this->a12 * $this->a33),
+ ($this->a11 * $this->a33 - $this->a13 * $this->a31),
+ ($this->a12 * $this->a31 - $this->a11 * $this->a32),
+ ($this->a12 * $this->a23 - $this->a13 * $this->a22),
+ ($this->a13 * $this->a21 - $this->a11 * $this->a23),
+ ($this->a11 * $this->a22 - $this->a12 * $this->a21)
+ );
+ }
+
+ /**
+ *
+ */
+ private function squareToQuadrilateral(
+ float $x0, float $y0, float $x1, float $y1,
+ float $x2, float $y2, float $x3, float $y3
+ ):self{
+ $dx3 = ($x0 - $x1 + $x2 - $x3);
+ $dy3 = ($y0 - $y1 + $y2 - $y3);
+
+ if($dx3 === 0.0 && $dy3 === 0.0){
+ // Affine
+ return $this->set(($x1 - $x0), ($x2 - $x1), $x0, ($y1 - $y0), ($y2 - $y1), $y0, 0.0, 0.0, 1.0);
+ }
+
+ $dx1 = ($x1 - $x2);
+ $dx2 = ($x3 - $x2);
+ $dy1 = ($y1 - $y2);
+ $dy2 = ($y3 - $y2);
+ $denominator = ($dx1 * $dy2 - $dx2 * $dy1);
+ $a13 = (($dx3 * $dy2 - $dx2 * $dy3) / $denominator);
+ $a23 = (($dx1 * $dy3 - $dx3 * $dy1) / $denominator);
+
+ return $this->set(
+ ($x1 - $x0 + $a13 * $x1),
+ ($x3 - $x0 + $a23 * $x3),
+ $x0,
+ ($y1 - $y0 + $a13 * $y1),
+ ($y3 - $y0 + $a23 * $y3),
+ $y0,
+ $a13,
+ $a23,
+ 1.0
+ );
+ }
+
+ /**
+ *
+ */
+ private function times(PerspectiveTransform $other):self{
+ return $this->set(
+ ($this->a11 * $other->a11 + $this->a21 * $other->a12 + $this->a31 * $other->a13),
+ ($this->a11 * $other->a21 + $this->a21 * $other->a22 + $this->a31 * $other->a23),
+ ($this->a11 * $other->a31 + $this->a21 * $other->a32 + $this->a31 * $other->a33),
+ ($this->a12 * $other->a11 + $this->a22 * $other->a12 + $this->a32 * $other->a13),
+ ($this->a12 * $other->a21 + $this->a22 * $other->a22 + $this->a32 * $other->a23),
+ ($this->a12 * $other->a31 + $this->a22 * $other->a32 + $this->a32 * $other->a33),
+ ($this->a13 * $other->a11 + $this->a23 * $other->a12 + $this->a33 * $other->a13),
+ ($this->a13 * $other->a21 + $this->a23 * $other->a22 + $this->a33 * $other->a23),
+ ($this->a13 * $other->a31 + $this->a23 * $other->a32 + $this->a33 * $other->a33)
+ );
+ }
+
+ /**
+ * @return array[] [$xValues, $yValues]
+ */
+ public function transformPoints(array $xValues, ?array $yValues = null):array{
+ $max = count($xValues);
+
+ if($yValues !== null){ // unused
+
+ for($i = 0; $i < $max; $i++){
+ $x = $xValues[$i];
+ $y = $yValues[$i];
+ $denominator = ($this->a13 * $x + $this->a23 * $y + $this->a33);
+ $xValues[$i] = (($this->a11 * $x + $this->a21 * $y + $this->a31) / $denominator);
+ $yValues[$i] = (($this->a12 * $x + $this->a22 * $y + $this->a32) / $denominator);
+ }
+
+ return [$xValues, $yValues];
+ }
+
+ for($i = 0; $i < $max; $i += 2){
+ $x = $xValues[$i];
+ $y = $xValues[($i + 1)];
+ $denominator = ($this->a13 * $x + $this->a23 * $y + $this->a33);
+ $xValues[$i] = (($this->a11 * $x + $this->a21 * $y + $this->a31) / $denominator);
+ $xValues[($i + 1)] = (($this->a12 * $x + $this->a22 * $y + $this->a32) / $denominator);
+ }
+
+ return [$xValues, []];
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php b/vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php
new file mode 100644
index 000000000..2444e193c
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * Class QRCodeDetectorException
+ *
+ * @created 01.12.2021
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2021 smiley
+ * @license MIT
+ */
+
+namespace chillerlan\QRCode\Detector;
+
+use chillerlan\QRCode\QRCodeException;
+
+/**
+ * An exception container
+ */
+final class QRCodeDetectorException extends QRCodeException{
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php b/vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php
new file mode 100644
index 000000000..92997a746
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * Class ResultPoint
+ *
+ * @created 17.01.2021
+ * @author ZXing Authors
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2021 Smiley
+ * @license Apache-2.0
+ */
+
+namespace chillerlan\QRCode\Detector;
+
+use function abs;
+
+/**
+ * Encapsulates a point of interest in an image containing a barcode. Typically, this
+ * would be the location of a finder pattern or the corner of the barcode, for example.
+ *
+ * @author Sean Owen
+ */
+abstract class ResultPoint{
+
+ protected float $x;
+ protected float $y;
+ protected float $estimatedModuleSize;
+
+ /**
+ *
+ */
+ public function __construct(float $x, float $y, float $estimatedModuleSize){
+ $this->x = $x;
+ $this->y = $y;
+ $this->estimatedModuleSize = $estimatedModuleSize;
+ }
+
+ /**
+ *
+ */
+ public function getX():float{
+ return $this->x;
+ }
+
+ /**
+ *
+ */
+ public function getY():float{
+ return $this->y;
+ }
+
+ /**
+ *
+ */
+ public function getEstimatedModuleSize():float{
+ return $this->estimatedModuleSize;
+ }
+
+ /**
+ * Determines if this finder pattern "about equals" a finder pattern at the stated
+ * position and size -- meaning, it is at nearly the same center with nearly the same size.
+ */
+ public function aboutEquals(float $moduleSize, float $i, float $j):bool{
+
+ if(abs($i - $this->y) <= $moduleSize && abs($j - $this->x) <= $moduleSize){
+ $moduleSizeDiff = abs($moduleSize - $this->estimatedModuleSize);
+
+ return $moduleSizeDiff <= 1.0 || $moduleSizeDiff <= $this->estimatedModuleSize;
+ }
+
+ return false;
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Helpers/BitBuffer.php b/vendor/chillerlan/php-qrcode/src/Helpers/BitBuffer.php
deleted file mode 100644
index de47f20f4..000000000
--- a/vendor/chillerlan/php-qrcode/src/Helpers/BitBuffer.php
+++ /dev/null
@@ -1,89 +0,0 @@
-<?php
-/**
- * Class BitBuffer
- *
- * @filesource BitBuffer.php
- * @created 25.11.2015
- * @package chillerlan\QRCode\Helpers
- * @author Smiley <smiley@chillerlan.net>
- * @copyright 2015 Smiley
- * @license MIT
- */
-
-namespace chillerlan\QRCode\Helpers;
-
-use function count, floor;
-
-/**
- * Holds the raw binary data
- */
-final class BitBuffer{
-
- /**
- * The buffer content
- *
- * @var int[]
- */
- protected array $buffer = [];
-
- /**
- * Length of the content (bits)
- */
- protected int $length = 0;
-
- /**
- * clears the buffer
- */
- public function clear():BitBuffer{
- $this->buffer = [];
- $this->length = 0;
-
- return $this;
- }
-
- /**
- * appends a sequence of bits
- */
- public function put(int $num, int $length):BitBuffer{
-
- for($i = 0; $i < $length; $i++){
- $this->putBit((($num >> ($length - $i - 1)) & 1) === 1);
- }
-
- return $this;
- }
-
- /**
- * appends a single bit
- */
- public function putBit(bool $bit):BitBuffer{
- $bufIndex = floor($this->length / 8);
-
- if(count($this->buffer) <= $bufIndex){
- $this->buffer[] = 0;
- }
-
- if($bit === true){
- $this->buffer[(int)$bufIndex] |= (0x80 >> ($this->length % 8));
- }
-
- $this->length++;
-
- return $this;
- }
-
- /**
- * returns the current buffer length
- */
- public function getLength():int{
- return $this->length;
- }
-
- /**
- * returns the buffer content
- */
- public function getBuffer():array{
- return $this->buffer;
- }
-
-}
diff --git a/vendor/chillerlan/php-qrcode/src/Helpers/Polynomial.php b/vendor/chillerlan/php-qrcode/src/Helpers/Polynomial.php
deleted file mode 100644
index c42e0831c..000000000
--- a/vendor/chillerlan/php-qrcode/src/Helpers/Polynomial.php
+++ /dev/null
@@ -1,178 +0,0 @@
-<?php
-/**
- * Class Polynomial
- *
- * @filesource Polynomial.php
- * @created 25.11.2015
- * @package chillerlan\QRCode\Helpers
- * @author Smiley <smiley@chillerlan.net>
- * @copyright 2015 Smiley
- * @license MIT
- */
-
-namespace chillerlan\QRCode\Helpers;
-
-use chillerlan\QRCode\QRCodeException;
-
-use function array_fill, count, sprintf;
-
-/**
- * Polynomial long division helpers
- *
- * @see http://www.thonky.com/qr-code-tutorial/error-correction-coding
- */
-final class Polynomial{
-
- /**
- * @see http://www.thonky.com/qr-code-tutorial/log-antilog-table
- */
- protected const table = [
- [ 1, 0], [ 2, 0], [ 4, 1], [ 8, 25], [ 16, 2], [ 32, 50], [ 64, 26], [128, 198],
- [ 29, 3], [ 58, 223], [116, 51], [232, 238], [205, 27], [135, 104], [ 19, 199], [ 38, 75],
- [ 76, 4], [152, 100], [ 45, 224], [ 90, 14], [180, 52], [117, 141], [234, 239], [201, 129],
- [143, 28], [ 3, 193], [ 6, 105], [ 12, 248], [ 24, 200], [ 48, 8], [ 96, 76], [192, 113],
- [157, 5], [ 39, 138], [ 78, 101], [156, 47], [ 37, 225], [ 74, 36], [148, 15], [ 53, 33],
- [106, 53], [212, 147], [181, 142], [119, 218], [238, 240], [193, 18], [159, 130], [ 35, 69],
- [ 70, 29], [140, 181], [ 5, 194], [ 10, 125], [ 20, 106], [ 40, 39], [ 80, 249], [160, 185],
- [ 93, 201], [186, 154], [105, 9], [210, 120], [185, 77], [111, 228], [222, 114], [161, 166],
- [ 95, 6], [190, 191], [ 97, 139], [194, 98], [153, 102], [ 47, 221], [ 94, 48], [188, 253],
- [101, 226], [202, 152], [137, 37], [ 15, 179], [ 30, 16], [ 60, 145], [120, 34], [240, 136],
- [253, 54], [231, 208], [211, 148], [187, 206], [107, 143], [214, 150], [177, 219], [127, 189],
- [254, 241], [225, 210], [223, 19], [163, 92], [ 91, 131], [182, 56], [113, 70], [226, 64],
- [217, 30], [175, 66], [ 67, 182], [134, 163], [ 17, 195], [ 34, 72], [ 68, 126], [136, 110],
- [ 13, 107], [ 26, 58], [ 52, 40], [104, 84], [208, 250], [189, 133], [103, 186], [206, 61],
- [129, 202], [ 31, 94], [ 62, 155], [124, 159], [248, 10], [237, 21], [199, 121], [147, 43],
- [ 59, 78], [118, 212], [236, 229], [197, 172], [151, 115], [ 51, 243], [102, 167], [204, 87],
- [133, 7], [ 23, 112], [ 46, 192], [ 92, 247], [184, 140], [109, 128], [218, 99], [169, 13],
- [ 79, 103], [158, 74], [ 33, 222], [ 66, 237], [132, 49], [ 21, 197], [ 42, 254], [ 84, 24],
- [168, 227], [ 77, 165], [154, 153], [ 41, 119], [ 82, 38], [164, 184], [ 85, 180], [170, 124],
- [ 73, 17], [146, 68], [ 57, 146], [114, 217], [228, 35], [213, 32], [183, 137], [115, 46],
- [230, 55], [209, 63], [191, 209], [ 99, 91], [198, 149], [145, 188], [ 63, 207], [126, 205],
- [252, 144], [229, 135], [215, 151], [179, 178], [123, 220], [246, 252], [241, 190], [255, 97],
- [227, 242], [219, 86], [171, 211], [ 75, 171], [150, 20], [ 49, 42], [ 98, 93], [196, 158],
- [149, 132], [ 55, 60], [110, 57], [220, 83], [165, 71], [ 87, 109], [174, 65], [ 65, 162],
- [130, 31], [ 25, 45], [ 50, 67], [100, 216], [200, 183], [141, 123], [ 7, 164], [ 14, 118],
- [ 28, 196], [ 56, 23], [112, 73], [224, 236], [221, 127], [167, 12], [ 83, 111], [166, 246],
- [ 81, 108], [162, 161], [ 89, 59], [178, 82], [121, 41], [242, 157], [249, 85], [239, 170],
- [195, 251], [155, 96], [ 43, 134], [ 86, 177], [172, 187], [ 69, 204], [138, 62], [ 9, 90],
- [ 18, 203], [ 36, 89], [ 72, 95], [144, 176], [ 61, 156], [122, 169], [244, 160], [245, 81],
- [247, 11], [243, 245], [251, 22], [235, 235], [203, 122], [139, 117], [ 11, 44], [ 22, 215],
- [ 44, 79], [ 88, 174], [176, 213], [125, 233], [250, 230], [233, 231], [207, 173], [131, 232],
- [ 27, 116], [ 54, 214], [108, 244], [216, 234], [173, 168], [ 71, 80], [142, 88], [ 1, 175],
- ];
-
- /**
- * @var int[]
- */
- protected array $num = [];
-
- /**
- * Polynomial constructor.
- */
- public function __construct(array $num = null, int $shift = null){
- $this->setNum($num ?? [1], $shift);
- }
-
- /**
- *
- */
- public function getNum():array{
- return $this->num;
- }
-
- /**
- * @param int[] $num
- * @param int|null $shift
- *
- * @return \chillerlan\QRCode\Helpers\Polynomial
- */
- public function setNum(array $num, int $shift = null):Polynomial{
- $offset = 0;
- $numCount = count($num);
-
- while($offset < $numCount && $num[$offset] === 0){
- $offset++;
- }
-
- $this->num = array_fill(0, $numCount - $offset + ($shift ?? 0), 0);
-
- for($i = 0; $i < $numCount - $offset; $i++){
- $this->num[$i] = $num[$i + $offset];
- }
-
- return $this;
- }
-
- /**
- * @param int[] $e
- *
- * @return \chillerlan\QRCode\Helpers\Polynomial
- */
- public function multiply(array $e):Polynomial{
- $n = array_fill(0, count($this->num) + count($e) - 1, 0);
-
- foreach($this->num as $i => $vi){
- $vi = $this->glog($vi);
-
- foreach($e as $j => $vj){
- $n[$i + $j] ^= $this->gexp($vi + $this->glog($vj));
- }
-
- }
-
- $this->setNum($n);
-
- return $this;
- }
-
- /**
- * @param int[] $e
- *
- * @return \chillerlan\QRCode\Helpers\Polynomial
- */
- public function mod(array $e):Polynomial{
- $n = $this->num;
-
- if(count($n) - count($e) < 0){
- return $this;
- }
-
- $ratio = $this->glog($n[0]) - $this->glog($e[0]);
-
- foreach($e as $i => $v){
- $n[$i] ^= $this->gexp($this->glog($v) + $ratio);
- }
-
- $this->setNum($n)->mod($e);
-
- return $this;
- }
-
- /**
- * @throws \chillerlan\QRCode\QRCodeException
- */
- public function glog(int $n):int{
-
- if($n < 1){
- throw new QRCodeException(sprintf('log(%s)', $n));
- }
-
- return Polynomial::table[$n][1];
- }
-
- /**
- *
- */
- public function gexp(int $n):int{
-
- if($n < 0){
- $n += 255;
- }
- elseif($n >= 256){
- $n -= 255;
- }
-
- return Polynomial::table[$n][0];
- }
-
-}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php b/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php
index 639bdd111..bf30f1bb0 100644
--- a/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php
+++ b/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php
@@ -2,9 +2,7 @@
/**
* Class QRCodeOutputException
*
- * @filesource QRCodeOutputException.php
* @created 09.12.2015
- * @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
@@ -14,4 +12,9 @@ namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\QRCodeException;
-class QRCodeOutputException extends QRCodeException{}
+/**
+ * An exception container
+ */
+final class QRCodeOutputException extends QRCodeException{
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QREps.php b/vendor/chillerlan/php-qrcode/src/Output/QREps.php
new file mode 100644
index 000000000..76dd50b37
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Output/QREps.php
@@ -0,0 +1,173 @@
+<?php
+/**
+ * Class QREps
+ *
+ * @created 09.05.2022
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2022 smiley
+ * @license MIT
+ */
+
+namespace chillerlan\QRCode\Output;
+
+use function array_values, count, date, implode, is_array, is_numeric, max, min, round, sprintf;
+
+/**
+ * Encapsulated Postscript (EPS) output
+ *
+ * @see https://github.com/t0k4rt/phpqrcode/blob/bb29e6eb77e0a2a85bb0eb62725e0adc11ff5a90/qrvect.php#L52-L137
+ * @see https://web.archive.org/web/20170818010030/http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/postscript/pdfs/5002.EPSF_Spec.pdf
+ * @see https://web.archive.org/web/20210419003859/https://www.adobe.com/content/dam/acom/en/devnet/actionscript/articles/PLRM.pdf
+ * @see https://github.com/chillerlan/php-qrcode/discussions/148
+ */
+class QREps extends QROutputAbstract{
+
+ public const MIME_TYPE = 'application/postscript';
+
+ /**
+ * @inheritDoc
+ */
+ public static function moduleValueIsValid($value):bool{
+
+ if(!is_array($value) || count($value) < 3){
+ return false;
+ }
+
+ // check the first values of the array
+ foreach(array_values($value) as $i => $val){
+
+ if($i > 3){
+ break;
+ }
+
+ if(!is_numeric($val)){
+ return false;
+ }
+
+ }
+
+ return true;
+ }
+
+ /**
+ * @param array $value
+ *
+ * @inheritDoc
+ */
+ protected function prepareModuleValue($value):string{
+ $values = [];
+
+ foreach(array_values($value) as $i => $val){
+
+ if($i > 3){
+ break;
+ }
+
+ // clamp value and convert from int 0-255 to float 0-1 RGB/CMYK range
+ $values[] = round((max(0, min(255, intval($val))) / 255), 6);
+ }
+
+ return $this->formatColor($values);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultModuleValue(bool $isDark):string{
+ return $this->formatColor(($isDark) ? [0.0, 0.0, 0.0] : [1.0, 1.0, 1.0]);
+ }
+
+ /**
+ * Set the color format string
+ *
+ * 4 values in the color array will be interpreted as CMYK, 3 as RGB
+ *
+ * @throws \chillerlan\QRCode\Output\QRCodeOutputException
+ */
+ protected function formatColor(array $values):string{
+ $count = count($values);
+
+ if($count < 3){
+ throw new QRCodeOutputException('invalid color value');
+ }
+
+ $format = ($count === 4)
+ // CMYK
+ ? '%f %f %f %f C'
+ // RGB
+ : '%f %f %f R';
+
+ return sprintf($format, ...$values);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function dump(?string $file = null):string{
+ [$width, $height] = $this->getOutputDimensions();
+
+ $eps = [
+ // main header
+ '%!PS-Adobe-3.0 EPSF-3.0',
+ '%%Creator: php-qrcode (https://github.com/chillerlan/php-qrcode)',
+ '%%Title: QR Code',
+ sprintf('%%%%CreationDate: %1$s', date('c')),
+ '%%DocumentData: Clean7Bit',
+ '%%LanguageLevel: 3',
+ sprintf('%%%%BoundingBox: 0 0 %s %s', $width, $height),
+ '%%EndComments',
+ // function definitions
+ '%%BeginProlog',
+ '/F { rectfill } def',
+ '/R { setrgbcolor } def',
+ '/C { setcmykcolor } def',
+ '%%EndProlog',
+ ];
+
+ if($this::moduleValueIsValid($this->options->bgColor)){
+ $eps[] = $this->prepareModuleValue($this->options->bgColor);
+ $eps[] = sprintf('0 0 %s %s F', $width, $height);
+ }
+
+ // create the path elements
+ $paths = $this->collectModules(fn(int $x, int $y, int $M_TYPE):string => $this->module($x, $y, $M_TYPE));
+
+ foreach($paths as $M_TYPE => $path){
+
+ if(empty($path)){
+ continue;
+ }
+
+ $eps[] = $this->getModuleValue($M_TYPE);
+ $eps[] = implode("\n", $path);
+ }
+
+ // end file
+ $eps[] = '%%EOF';
+
+ $data = implode("\n", $eps);
+
+ $this->saveToFile($data, $file);
+
+ return $data;
+ }
+
+ /**
+ * Returns a path segment for a single module
+ */
+ protected function module(int $x, int $y, int $M_TYPE):string{
+
+ if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){
+ return '';
+ }
+
+ $outputX = ($x * $this->scale);
+ // Actual size - one block = Topmost y pos.
+ $top = ($this->length - $this->scale);
+ // Apparently y-axis is inverted (y0 is at bottom and not top) in EPS, so we have to switch the y-axis here
+ $outputY = ($top - ($y * $this->scale));
+
+ return sprintf('%d %d %d %d F', $outputX, $outputY, $this->scale, $this->scale);
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php b/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php
index a15ae9ff3..8f2482cba 100644
--- a/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php
+++ b/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php
@@ -2,24 +2,20 @@
/**
* Class QRFpdf
*
- * https://github.com/chillerlan/php-qrcode/pull/49
- *
- * @filesource QRFpdf.php
* @created 03.06.2020
- * @package chillerlan\QRCode\Output
* @author Maximilian Kresse
- *
* @license MIT
+ *
+ * @see https://github.com/chillerlan/php-qrcode/pull/49
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\Data\QRMatrix;
-use chillerlan\QRCode\QRCodeException;
use chillerlan\Settings\SettingsContainerInterface;
use FPDF;
-use function array_values, class_exists, count, is_array;
+use function array_values, class_exists, count, intval, is_array, is_numeric, max, min;
/**
* QRFpdf output module (requires fpdf)
@@ -29,12 +25,23 @@ use function array_values, class_exists, count, is_array;
*/
class QRFpdf extends QROutputAbstract{
+ public const MIME_TYPE = 'application/pdf';
+
+ protected FPDF $fpdf;
+ protected ?array $prevColor = null;
+
+ /**
+ * QRFpdf constructor.
+ *
+ * @throws \chillerlan\QRCode\Output\QRCodeOutputException
+ */
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
if(!class_exists(FPDF::class)){
// @codeCoverageIgnoreStart
- throw new QRCodeException(
- 'The QRFpdf output requires FPDF as dependency but the class "\FPDF" couldn\'t be found.'
+ throw new QRCodeOutputException(
+ 'The QRFpdf output requires FPDF (https://github.com/Setasign/FPDF)'.
+ ' as dependency but the class "\\FPDF" couldn\'t be found.'
);
// @codeCoverageIgnoreEnd
}
@@ -45,69 +52,128 @@ class QRFpdf extends QROutputAbstract{
/**
* @inheritDoc
*/
- protected function setModuleValues():void{
+ public static function moduleValueIsValid($value):bool{
+
+ if(!is_array($value) || count($value) < 3){
+ return false;
+ }
- foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
- $v = $this->options->moduleValues[$M_TYPE] ?? null;
+ // check the first 3 values of the array
+ foreach(array_values($value) as $i => $val){
- if(!is_array($v) || count($v) < 3){
- $this->moduleValues[$M_TYPE] = $defaultValue
- ? [0, 0, 0]
- : [255, 255, 255];
+ if($i > 2){
+ break;
}
- else{
- $this->moduleValues[$M_TYPE] = array_values($v);
+
+ if(!is_numeric($val)){
+ return false;
}
}
+ return true;
}
/**
- * @inheritDoc
+ * @param array $value
*
- * @return string|\FPDF
+ * @inheritDoc
+ * @throws \chillerlan\QRCode\Output\QRCodeOutputException
+ */
+ protected function prepareModuleValue($value):array{
+ $values = [];
+
+ foreach(array_values($value) as $i => $val){
+
+ if($i > 2){
+ break;
+ }
+
+ $values[] = max(0, min(255, intval($val)));
+ }
+
+ if(count($values) !== 3){
+ throw new QRCodeOutputException('invalid color value');
+ }
+
+ return $values;
+ }
+
+ /**
+ * @inheritDoc
*/
- public function dump(string $file = null){
- $file ??= $this->options->cachefile;
+ protected function getDefaultModuleValue(bool $isDark):array{
+ return ($isDark) ? [0, 0, 0] : [255, 255, 255];
+ }
- $fpdf = new FPDF('P', $this->options->fpdfMeasureUnit, [$this->length, $this->length]);
+ /**
+ * Initializes an FPDF instance
+ */
+ protected function initFPDF():FPDF{
+ $fpdf = new FPDF('P', $this->options->fpdfMeasureUnit, $this->getOutputDimensions());
$fpdf->AddPage();
- $prevColor = null;
+ return $fpdf;
+ }
- foreach($this->matrix->matrix() as $y => $row){
+ /**
+ * @inheritDoc
+ *
+ * @return string|\FPDF
+ */
+ public function dump(?string $file = null, ?FPDF $fpdf = null){
+ $this->fpdf = ($fpdf ?? $this->initFPDF());
- foreach($row as $x => $M_TYPE){
- /** @var int $M_TYPE */
- $color = $this->moduleValues[$M_TYPE];
+ if($this::moduleValueIsValid($this->options->bgColor)){
+ $bgColor = $this->prepareModuleValue($this->options->bgColor);
+ [$width, $height] = $this->getOutputDimensions();
- if($prevColor === null || $prevColor !== $color){
- /** @phan-suppress-next-line PhanParamTooFewUnpack */
- $fpdf->SetFillColor(...$color);
- $prevColor = $color;
- }
+ /** @phan-suppress-next-line PhanParamTooFewUnpack */
+ $this->fpdf->SetFillColor(...$bgColor);
+ $this->fpdf->Rect(0, 0, $width, $height, 'F');
+ }
- $fpdf->Rect($x * $this->scale, $y * $this->scale, 1 * $this->scale, 1 * $this->scale, 'F');
- }
+ $this->prevColor = null;
+ foreach($this->matrix->getMatrix() as $y => $row){
+ foreach($row as $x => $M_TYPE){
+ $this->module($x, $y, $M_TYPE);
+ }
}
if($this->options->returnResource){
- return $fpdf;
+ return $this->fpdf;
}
- $pdfData = $fpdf->Output('S');
+ $pdfData = $this->fpdf->Output('S');
- if($file !== null){
- $this->saveToFile($pdfData, $file);
- }
+ $this->saveToFile($pdfData, $file);
- if($this->options->imageBase64){
- $pdfData = sprintf('data:application/pdf;base64,%s', base64_encode($pdfData));
+ if($this->options->outputBase64){
+ $pdfData = $this->toBase64DataURI($pdfData);
}
return $pdfData;
}
+ /**
+ * Renders a single module
+ */
+ protected function module(int $x, int $y, int $M_TYPE):void{
+
+ if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){
+ return;
+ }
+
+ $color = $this->getModuleValue($M_TYPE);
+
+ if($color !== null && $color !== $this->prevColor){
+ /** @phan-suppress-next-line PhanParamTooFewUnpack */
+ $this->fpdf->SetFillColor(...$color);
+ $this->prevColor = $color;
+ }
+
+ $this->fpdf->Rect(($x * $this->scale), ($y * $this->scale), $this->scale, $this->scale, 'F');
+ }
+
}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php
new file mode 100644
index 000000000..25db1c902
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php
@@ -0,0 +1,400 @@
+<?php
+/**
+ * Class QRGdImage
+ *
+ * @created 05.12.2015
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2015 Smiley
+ * @license MIT
+ *
+ * @noinspection PhpComposerExtensionStubsInspection
+ */
+
+namespace chillerlan\QRCode\Output;
+
+use chillerlan\QRCode\Data\QRMatrix;
+use chillerlan\Settings\SettingsContainerInterface;
+use ErrorException;
+use Throwable;
+use function array_values, count, extension_loaded, imagebmp, imagecolorallocate, imagecolortransparent,
+ imagecreatetruecolor, imagedestroy, imagefilledellipse, imagefilledrectangle, imagegif, imagejpeg, imagepng,
+ imagescale, imagetypes, imagewebp, intdiv, intval, is_array, is_numeric, max, min, ob_end_clean, ob_get_contents, ob_start,
+ restore_error_handler, set_error_handler, sprintf;
+use const IMG_BMP, IMG_GIF, IMG_JPG, IMG_PNG, IMG_WEBP;
+
+/**
+ * Converts the matrix into GD images, raw or base64 output (requires ext-gd)
+ *
+ * @see https://php.net/manual/book.image.php
+ *
+ * @deprecated 5.0.0 this class will be made abstract in future versions,
+ * calling it directly is deprecated - use one of the child classes instead
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ */
+class QRGdImage extends QROutputAbstract{
+
+ /**
+ * The GD image resource
+ *
+ * @see imagecreatetruecolor()
+ * @var resource|\GdImage
+ *
+ * @todo: add \GdImage type in v6
+ */
+ protected $image;
+
+ /**
+ * The allocated background color
+ *
+ * @see \imagecolorallocate()
+ */
+ protected int $background;
+
+ /**
+ * Whether we're running in upscale mode (scale < 20)
+ *
+ * @see \chillerlan\QRCode\QROptions::$drawCircularModules
+ */
+ protected bool $upscaled = false;
+
+ /**
+ * @inheritDoc
+ *
+ * @throws \chillerlan\QRCode\Output\QRCodeOutputException
+ * @noinspection PhpMissingParentConstructorInspection
+ */
+ public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
+ $this->options = $options;
+ $this->matrix = $matrix;
+
+ $this->checkGD();
+
+ if($this->options->invertMatrix){
+ $this->matrix->invert();
+ }
+
+ $this->copyVars();
+ $this->setMatrixDimensions();
+ }
+
+ /**
+ * Checks whether GD is installed and if the given mode is supported
+ *
+ * @return void
+ * @throws \chillerlan\QRCode\Output\QRCodeOutputException
+ * @codeCoverageIgnore
+ */
+ protected function checkGD():void{
+
+ if(!extension_loaded('gd')){
+ throw new QRCodeOutputException('ext-gd not loaded');
+ }
+
+ $modes = [
+ self::GDIMAGE_BMP => IMG_BMP,
+ self::GDIMAGE_GIF => IMG_GIF,
+ self::GDIMAGE_JPG => IMG_JPG,
+ self::GDIMAGE_PNG => IMG_PNG,
+ self::GDIMAGE_WEBP => IMG_WEBP,
+ ];
+
+ // likely using default or custom output
+ if(!isset($modes[$this->options->outputType])){
+ return;
+ }
+
+ $mode = $modes[$this->options->outputType];
+
+ if((imagetypes() & $mode) !== $mode){
+ throw new QRCodeOutputException(sprintf('output mode "%s" not supported', $this->options->outputType));
+ }
+
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public static function moduleValueIsValid($value):bool{
+
+ if(!is_array($value) || count($value) < 3){
+ return false;
+ }
+
+ // check the first 3 values of the array
+ foreach(array_values($value) as $i => $val){
+
+ if($i > 2){
+ break;
+ }
+
+ if(!is_numeric($val)){
+ return false;
+ }
+
+ }
+
+ return true;
+ }
+
+ /**
+ * @param array $value
+ *
+ * @inheritDoc
+ * @throws \chillerlan\QRCode\Output\QRCodeOutputException
+ */
+ protected function prepareModuleValue($value):int{
+ $values = [];
+
+ foreach(array_values($value) as $i => $val){
+
+ if($i > 2){
+ break;
+ }
+
+ $values[] = max(0, min(255, intval($val)));
+ }
+
+ /** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
+ $color = imagecolorallocate($this->image, ...$values);
+
+ if($color === false){
+ throw new QRCodeOutputException('could not set color: imagecolorallocate() error');
+ }
+
+ return $color;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultModuleValue(bool $isDark):int{
+ return $this->prepareModuleValue(($isDark) ? [0, 0, 0] : [255, 255, 255]);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @return string|resource|\GdImage
+ *
+ * @phan-suppress PhanUndeclaredTypeReturnType, PhanTypeMismatchReturn
+ * @throws \ErrorException
+ */
+ public function dump(?string $file = null){
+
+ set_error_handler(function(int $errno, string $errstr):bool{
+ throw new ErrorException($errstr, $errno);
+ });
+
+ $this->image = $this->createImage();
+ // set module values after image creation because we need the GdImage instance
+ $this->setModuleValues();
+ $this->setBgColor();
+
+ imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $this->background);
+
+ $this->drawImage();
+
+ if($this->upscaled){
+ // scale down to the expected size
+ $this->image = imagescale($this->image, ($this->length / 10), ($this->length / 10));
+ $this->upscaled = false;
+ }
+
+ // set transparency after scaling, otherwise it would be undone
+ // @see https://www.php.net/manual/en/function.imagecolortransparent.php#77099
+ $this->setTransparencyColor();
+
+ if($this->options->returnResource){
+ restore_error_handler();
+
+ return $this->image;
+ }
+
+ $imageData = $this->dumpImage();
+
+ $this->saveToFile($imageData, $file);
+
+ if($this->options->outputBase64){
+ // @todo: remove mime parameter in v6
+ $imageData = $this->toBase64DataURI($imageData, 'image/'.$this->options->outputType);
+ }
+
+ restore_error_handler();
+
+ return $imageData;
+ }
+
+ /**
+ * Creates a new GdImage resource and scales it if necessary
+ *
+ * we're scaling the image up in order to draw crisp round circles, otherwise they appear square-y on small scales
+ *
+ * @see https://github.com/chillerlan/php-qrcode/issues/23
+ *
+ * @return \GdImage|resource
+ */
+ protected function createImage(){
+
+ if($this->drawCircularModules && $this->options->gdImageUseUpscale && $this->options->scale < 20){
+ // increase the initial image size by 10
+ $this->length *= 10;
+ $this->scale *= 10;
+ $this->upscaled = true;
+ }
+
+ return imagecreatetruecolor($this->length, $this->length);
+ }
+
+ /**
+ * Sets the background color
+ */
+ protected function setBgColor():void{
+
+ if(isset($this->background)){
+ return;
+ }
+
+ if($this::moduleValueIsValid($this->options->bgColor)){
+ $this->background = $this->prepareModuleValue($this->options->bgColor);
+
+ return;
+ }
+
+ $this->background = $this->prepareModuleValue([255, 255, 255]);
+ }
+
+ /**
+ * Sets the transparency color
+ */
+ protected function setTransparencyColor():void{
+
+ // @todo: the jpg skip can be removed in v6
+ if($this->options->outputType === QROutputInterface::GDIMAGE_JPG || !$this->options->imageTransparent){
+ return;
+ }
+
+ $transparencyColor = $this->background;
+
+ if($this::moduleValueIsValid($this->options->transparencyColor)){
+ $transparencyColor = $this->prepareModuleValue($this->options->transparencyColor);
+ }
+
+ imagecolortransparent($this->image, $transparencyColor);
+ }
+
+ /**
+ * Draws the QR image
+ */
+ protected function drawImage():void{
+ foreach($this->matrix->getMatrix() as $y => $row){
+ foreach($row as $x => $M_TYPE){
+ $this->module($x, $y, $M_TYPE);
+ }
+ }
+ }
+
+ /**
+ * Creates a single QR pixel with the given settings
+ */
+ protected function module(int $x, int $y, int $M_TYPE):void{
+
+ if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){
+ return;
+ }
+
+ $color = $this->getModuleValue($M_TYPE);
+
+ if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){
+ imagefilledellipse(
+ $this->image,
+ (($x * $this->scale) + intdiv($this->scale, 2)),
+ (($y * $this->scale) + intdiv($this->scale, 2)),
+ (int)($this->circleDiameter * $this->scale),
+ (int)($this->circleDiameter * $this->scale),
+ $color
+ );
+
+ return;
+ }
+
+ imagefilledrectangle(
+ $this->image,
+ ($x * $this->scale),
+ ($y * $this->scale),
+ (($x + 1) * $this->scale),
+ (($y + 1) * $this->scale),
+ $color
+ );
+ }
+
+ /**
+ * Renders the image with the gdimage function for the desired output
+ *
+ * @see \imagebmp()
+ * @see \imagegif()
+ * @see \imagejpeg()
+ * @see \imagepng()
+ * @see \imagewebp()
+ *
+ * @todo: v6.0: make abstract and call from child classes
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ * @codeCoverageIgnore
+ */
+ protected function renderImage():void{
+
+ switch($this->options->outputType){
+ case QROutputInterface::GDIMAGE_BMP:
+ imagebmp($this->image, null, ($this->options->quality > 0));
+ break;
+ case QROutputInterface::GDIMAGE_GIF:
+ imagegif($this->image);
+ break;
+ case QROutputInterface::GDIMAGE_JPG:
+ imagejpeg($this->image, null, max(-1, min(100, $this->options->quality)));
+ break;
+ case QROutputInterface::GDIMAGE_WEBP:
+ imagewebp($this->image, null, max(-1, min(100, $this->options->quality)));
+ break;
+ // silently default to png output
+ case QROutputInterface::GDIMAGE_PNG:
+ default:
+ imagepng($this->image, null, max(-1, min(9, $this->options->quality)));
+ }
+
+ }
+
+ /**
+ * Creates the final image by calling the desired GD output function
+ *
+ * @throws \chillerlan\QRCode\Output\QRCodeOutputException
+ */
+ protected function dumpImage():string{
+ $exception = null;
+ $imageData = null;
+
+ ob_start();
+
+ try{
+ $this->renderImage();
+
+ $imageData = ob_get_contents();
+ imagedestroy($this->image);
+ }
+ // not going to cover edge cases
+ // @codeCoverageIgnoreStart
+ catch(Throwable $e){
+ $exception = $e;
+ }
+ // @codeCoverageIgnoreEnd
+
+ ob_end_clean();
+
+ // throw here in case an exception happened within the output buffer
+ if($exception instanceof Throwable){
+ throw new QRCodeOutputException($exception->getMessage());
+ }
+
+ return $imageData;
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php
new file mode 100644
index 000000000..268ebe7c2
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageBMP.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Class QRGdImageBMP
+ *
+ * @created 25.10.2023
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2023 smiley
+ * @license MIT
+ *
+ * @noinspection PhpComposerExtensionStubsInspection
+ */
+
+namespace chillerlan\QRCode\Output;
+
+use function imagebmp;
+
+/**
+ * GdImage bmp output
+ *
+ * @see \imagebmp()
+ */
+class QRGdImageBMP extends QRGdImage{
+
+ public const MIME_TYPE = 'image/bmp';
+
+ /**
+ * @inheritDoc
+ */
+ protected function renderImage():void{
+ imagebmp($this->image, null, ($this->options->quality > 0));
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php
new file mode 100644
index 000000000..a02130907
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageGIF.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Class QRGdImageGIF
+ *
+ * @created 25.10.2023
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2023 smiley
+ * @license MIT
+ *
+ * @noinspection PhpComposerExtensionStubsInspection
+ */
+
+namespace chillerlan\QRCode\Output;
+
+use function imagegif;
+
+/**
+ * GdImage gif output
+ *
+ * @see \imagegif()
+ */
+class QRGdImageGIF extends QRGdImage{
+
+ public const MIME_TYPE = 'image/gif';
+
+ /**
+ * @inheritDoc
+ */
+ protected function renderImage():void{
+ imagegif($this->image);
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php
new file mode 100644
index 000000000..6be36e2fe
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageJPEG.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Class QRGdImageJPEG
+ *
+ * @created 25.10.2023
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2023 smiley
+ * @license MIT
+ *
+ * @noinspection PhpComposerExtensionStubsInspection
+ */
+
+namespace chillerlan\QRCode\Output;
+
+use function imagejpeg, max, min;
+
+/**
+ * GdImage jpeg output
+ *
+ * @see \imagejpeg()
+ */
+class QRGdImageJPEG extends QRGdImage{
+
+ public const MIME_TYPE = 'image/jpg';
+
+ /**
+ * @inheritDoc
+ */
+ protected function setTransparencyColor():void{
+ // noop - transparency is not supported
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function renderImage():void{
+ imagejpeg($this->image, null, max(-1, min(100, $this->options->quality)));
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php
new file mode 100644
index 000000000..2db3fd5b4
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Output/QRGdImagePNG.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Class QRGdImagePNG
+ *
+ * @created 25.10.2023
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2023 smiley
+ * @license MIT
+ *
+ * @noinspection PhpComposerExtensionStubsInspection
+ */
+
+namespace chillerlan\QRCode\Output;
+
+use function imagepng, max, min;
+
+/**
+ * GdImage png output
+ *
+ * @see \imagepng()
+ */
+class QRGdImagePNG extends QRGdImage{
+
+ public const MIME_TYPE = 'image/png';
+
+ /**
+ * @inheritDoc
+ */
+ protected function renderImage():void{
+ imagepng($this->image, null, max(-1, min(9, $this->options->quality)));
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php
new file mode 100644
index 000000000..cf8dfa9a5
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Output/QRGdImageWEBP.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Class QRGdImageWEBP
+ *
+ * @created 25.10.2023
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2023 smiley
+ * @license MIT
+ *
+ * @noinspection PhpComposerExtensionStubsInspection
+ */
+
+namespace chillerlan\QRCode\Output;
+
+use function imagewebp, max, min;
+
+/**
+ * GdImage webp output
+ *
+ * @see \imagewebp()
+ */
+class QRGdImageWEBP extends QRGdImage{
+
+ public const MIME_TYPE = 'image/webp';
+
+ /**
+ * @inheritDoc
+ */
+ protected function renderImage():void{
+ imagewebp($this->image, null, max(-1, min(100, $this->options->quality)));
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRImage.php b/vendor/chillerlan/php-qrcode/src/Output/QRImage.php
index 8f533d341..cda496d36 100644
--- a/vendor/chillerlan/php-qrcode/src/Output/QRImage.php
+++ b/vendor/chillerlan/php-qrcode/src/Output/QRImage.php
@@ -2,216 +2,18 @@
/**
* Class QRImage
*
- * @filesource QRImage.php
- * @created 05.12.2015
- * @package chillerlan\QRCode\Output
- * @author Smiley <smiley@chillerlan.net>
- * @copyright 2015 Smiley
+ * @created 14.12.2021
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2021 smiley
* @license MIT
- *
- * @noinspection PhpComposerExtensionStubsInspection
*/
namespace chillerlan\QRCode\Output;
-use chillerlan\QRCode\Data\QRMatrix;
-use chillerlan\QRCode\{QRCode, QRCodeException};
-use chillerlan\Settings\SettingsContainerInterface;
-use Exception;
-
-use function array_values, base64_encode, call_user_func, count, extension_loaded, imagecolorallocate, imagecolortransparent,
- imagecreatetruecolor, imagedestroy, imagefilledrectangle, imagegif, imagejpeg, imagepng, in_array,
- is_array, ob_end_clean, ob_get_contents, ob_start, range, sprintf;
-
/**
- * Converts the matrix into GD images, raw or base64 output (requires ext-gd)
- *
- * @see http://php.net/manual/book.image.php
+ * @deprecated 5.0.0 backward compatibility, use QRGdImage instead
+ * @see \chillerlan\QRCode\Output\QRGdImage
*/
-class QRImage extends QROutputAbstract{
-
- /**
- * GD image types that support transparency
- *
- * @var string[]
- */
- protected const TRANSPARENCY_TYPES = [
- QRCode::OUTPUT_IMAGE_PNG,
- QRCode::OUTPUT_IMAGE_GIF,
- ];
-
- protected string $defaultMode = QRCode::OUTPUT_IMAGE_PNG;
-
- /**
- * The GD image resource
- *
- * @see imagecreatetruecolor()
- * @var resource|\GdImage
- *
- * @phan-suppress PhanUndeclaredTypeProperty
- */
- protected $image;
-
- /**
- * @inheritDoc
- *
- * @throws \chillerlan\QRCode\QRCodeException
- */
- public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
-
- if(!extension_loaded('gd')){
- throw new QRCodeException('ext-gd not loaded'); // @codeCoverageIgnore
- }
-
- parent::__construct($options, $matrix);
- }
-
- /**
- * @inheritDoc
- */
- protected function setModuleValues():void{
-
- foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
- $v = $this->options->moduleValues[$M_TYPE] ?? null;
-
- if(!is_array($v) || count($v) < 3){
- $this->moduleValues[$M_TYPE] = $defaultValue
- ? [0, 0, 0]
- : [255, 255, 255];
- }
- else{
- $this->moduleValues[$M_TYPE] = array_values($v);
- }
-
- }
-
- }
-
- /**
- * @inheritDoc
- *
- * @return string|resource|\GdImage
- *
- * @phan-suppress PhanUndeclaredTypeReturnType, PhanTypeMismatchReturn
- */
- public function dump(string $file = null){
- $file ??= $this->options->cachefile;
-
- $this->image = imagecreatetruecolor($this->length, $this->length);
-
- // avoid: Indirect modification of overloaded property $imageTransparencyBG has no effect
- // https://stackoverflow.com/a/10455217
- $tbg = $this->options->imageTransparencyBG;
- /** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
- $background = imagecolorallocate($this->image, ...$tbg);
-
- if((bool)$this->options->imageTransparent && in_array($this->options->outputType, $this::TRANSPARENCY_TYPES, true)){
- imagecolortransparent($this->image, $background);
- }
-
- imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $background);
-
- foreach($this->matrix->matrix() as $y => $row){
- foreach($row as $x => $M_TYPE){
- $this->setPixel($x, $y, $this->moduleValues[$M_TYPE]);
- }
- }
-
- if($this->options->returnResource){
- return $this->image;
- }
-
- $imageData = $this->dumpImage();
-
- if($file !== null){
- $this->saveToFile($imageData, $file);
- }
-
- if($this->options->imageBase64){
- $imageData = sprintf('data:image/%s;base64,%s', $this->options->outputType, base64_encode($imageData));
- }
-
- return $imageData;
- }
-
- /**
- * Creates a single QR pixel with the given settings
- */
- protected function setPixel(int $x, int $y, array $rgb):void{
- imagefilledrectangle(
- $this->image,
- $x * $this->scale,
- $y * $this->scale,
- ($x + 1) * $this->scale,
- ($y + 1) * $this->scale,
- /** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
- imagecolorallocate($this->image, ...$rgb)
- );
- }
-
- /**
- * Creates the final image by calling the desired GD output function
- *
- * @throws \chillerlan\QRCode\Output\QRCodeOutputException
- */
- protected function dumpImage():string{
- ob_start();
-
- try{
- call_user_func([$this, $this->outputMode ?? $this->defaultMode]);
- }
- // not going to cover edge cases
- // @codeCoverageIgnoreStart
- catch(Exception $e){
- throw new QRCodeOutputException($e->getMessage());
- }
- // @codeCoverageIgnoreEnd
-
- $imageData = ob_get_contents();
- imagedestroy($this->image);
-
- ob_end_clean();
-
- return $imageData;
- }
-
- /**
- * PNG output
- *
- * @return void
- */
- protected function png():void{
- imagepng(
- $this->image,
- null,
- in_array($this->options->pngCompression, range(-1, 9), true)
- ? $this->options->pngCompression
- : -1
- );
- }
-
- /**
- * Jiff - like... JitHub!
- *
- * @return void
- */
- protected function gif():void{
- imagegif($this->image);
- }
-
- /**
- * JPG output
- *
- * @return void
- */
- protected function jpg():void{
- imagejpeg(
- $this->image,
- null,
- in_array($this->options->jpegQuality, range(0, 100), true)
- ? $this->options->jpegQuality
- : 85
- );
- }
+class QRImage extends QRGdImage{
}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php b/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php
index 49516d30e..214311a93 100644
--- a/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php
+++ b/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php
@@ -2,9 +2,7 @@
/**
* Class QRImagick
*
- * @filesource QRImagick.php
* @created 04.07.2018
- * @package chillerlan\QRCode\Output
* @author smiley <smiley@chillerlan.net>
* @copyright 2018 smiley
* @license MIT
@@ -15,51 +13,98 @@
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\Data\QRMatrix;
-use chillerlan\QRCode\QRCodeException;
use chillerlan\Settings\SettingsContainerInterface;
-use Imagick, ImagickDraw, ImagickPixel;
-
-use function extension_loaded, is_string;
+use finfo, Imagick, ImagickDraw, ImagickPixel;
+use function extension_loaded, in_array, is_string, max, min, preg_match, strlen;
+use const FILEINFO_MIME_TYPE;
/**
* ImageMagick output module (requires ext-imagick)
*
- * @see http://php.net/manual/book.imagick.php
- * @see http://phpimagick.com
+ * @see https://php.net/manual/book.imagick.php
+ * @see https://phpimagick.com
*/
class QRImagick extends QROutputAbstract{
+ /**
+ * The main image instance
+ */
protected Imagick $imagick;
/**
+ * The main draw instance
+ */
+ protected ImagickDraw $imagickDraw;
+
+ /**
+ * The allocated background color
+ */
+ protected ImagickPixel $backgroundColor;
+
+ /**
* @inheritDoc
+ *
+ * @throws \chillerlan\QRCode\Output\QRCodeOutputException
*/
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
- if(!extension_loaded('imagick')){
- throw new QRCodeException('ext-imagick not loaded'); // @codeCoverageIgnore
+ foreach(['fileinfo', 'imagick'] as $ext){
+ if(!extension_loaded($ext)){
+ throw new QRCodeOutputException(sprintf('ext-%s not loaded', $ext)); // @codeCoverageIgnore
+ }
}
parent::__construct($options, $matrix);
}
/**
+ * note: we're not necessarily validating the several values, just checking the general syntax
+ *
+ * @see https://www.php.net/manual/imagickpixel.construct.php
* @inheritDoc
*/
- protected function setModuleValues():void{
+ public static function moduleValueIsValid($value):bool{
- foreach($this::DEFAULT_MODULE_VALUES as $type => $defaultValue){
- $v = $this->options->moduleValues[$type] ?? null;
+ if(!is_string($value)){
+ return false;
+ }
- if(!is_string($v)){
- $this->moduleValues[$type] = $defaultValue
- ? new ImagickPixel($this->options->markupDark)
- : new ImagickPixel($this->options->markupLight);
- }
- else{
- $this->moduleValues[$type] = new ImagickPixel($v);
- }
+ $value = trim($value);
+
+ // hex notation
+ // #rgb(a)
+ // #rrggbb(aa)
+ // #rrrrggggbbbb(aaaa)
+ // ...
+ if(preg_match('/^#[a-f\d]+$/i', $value) && in_array((strlen($value) - 1), [3, 4, 6, 8, 9, 12, 16, 24, 32], true)){
+ return true;
+ }
+
+ // css (-like) func(...values)
+ if(preg_match('#^(graya?|hs(b|la?)|rgba?)\([\d .,%]+\)$#i', $value)){
+ return true;
}
+
+ // predefined css color
+ if(preg_match('/^[a-z]+$/i', $value)){
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function prepareModuleValue($value):ImagickPixel{
+ return new ImagickPixel($value);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultModuleValue(bool $isDark):ImagickPixel{
+ return $this->prepareModuleValue(($isDark) ? '#000' : '#fff');
}
/**
@@ -67,18 +112,14 @@ class QRImagick extends QROutputAbstract{
*
* @return string|\Imagick
*/
- public function dump(string $file = null){
- $file ??= $this->options->cachefile;
- $this->imagick = new Imagick;
-
- $this->imagick->newImage(
- $this->length,
- $this->length,
- new ImagickPixel($this->options->imagickBG ?? 'transparent'),
- $this->options->imagickFormat
- );
+ public function dump(?string $file = null){
+ $this->setBgColor();
+
+ $this->imagick = $this->createImage();
$this->drawImage();
+ // set transparency color after all operations
+ $this->setTransparencyColor();
if($this->options->returnResource){
return $this->imagick;
@@ -86,34 +127,109 @@ class QRImagick extends QROutputAbstract{
$imageData = $this->imagick->getImageBlob();
- if($file !== null){
- $this->saveToFile($imageData, $file);
+ $this->imagick->destroy();
+
+ $this->saveToFile($imageData, $file);
+
+ if($this->options->outputBase64){
+ $imageData = $this->toBase64DataURI($imageData, (new finfo(FILEINFO_MIME_TYPE))->buffer($imageData));
}
return $imageData;
}
/**
+ * Sets the background color
+ */
+ protected function setBgColor():void{
+
+ if($this::moduleValueIsValid($this->options->bgColor)){
+ $this->backgroundColor = $this->prepareModuleValue($this->options->bgColor);
+
+ return;
+ }
+
+ $this->backgroundColor = $this->prepareModuleValue('white');
+ }
+
+ /**
+ * Creates a new Imagick instance
+ */
+ protected function createImage():Imagick{
+ $imagick = new Imagick;
+ [$width, $height] = $this->getOutputDimensions();
+
+ $imagick->newImage($width, $height, $this->backgroundColor, $this->options->imagickFormat);
+
+ if($this->options->quality > -1){
+ $imagick->setImageCompressionQuality(max(0, min(100, $this->options->quality)));
+ }
+
+ return $imagick;
+ }
+
+ /**
+ * Sets the transparency color
+ */
+ protected function setTransparencyColor():void{
+
+ if(!$this->options->imageTransparent){
+ return;
+ }
+
+ $transparencyColor = $this->backgroundColor;
+
+ if($this::moduleValueIsValid($this->options->transparencyColor)){
+ $transparencyColor = $this->prepareModuleValue($this->options->transparencyColor);
+ }
+
+ $this->imagick->transparentPaintImage($transparencyColor, 0.0, 10, false);
+ }
+
+ /**
* Creates the QR image via ImagickDraw
*/
protected function drawImage():void{
- $draw = new ImagickDraw;
+ $this->imagickDraw = new ImagickDraw;
+ $this->imagickDraw->setStrokeWidth(0);
- foreach($this->matrix->matrix() as $y => $row){
+ foreach($this->matrix->getMatrix() as $y => $row){
foreach($row as $x => $M_TYPE){
- $draw->setStrokeColor($this->moduleValues[$M_TYPE]);
- $draw->setFillColor($this->moduleValues[$M_TYPE]);
- $draw->rectangle(
- $x * $this->scale,
- $y * $this->scale,
- ($x + 1) * $this->scale,
- ($y + 1) * $this->scale
- );
-
+ $this->module($x, $y, $M_TYPE);
}
}
- $this->imagick->drawImage($draw);
+ $this->imagick->drawImage($this->imagickDraw);
+ }
+
+ /**
+ * draws a single pixel at the given position
+ */
+ protected function module(int $x, int $y, int $M_TYPE):void{
+
+ if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){
+ return;
+ }
+
+ $this->imagickDraw->setFillColor($this->getModuleValue($M_TYPE));
+
+ if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){
+ $this->imagickDraw->circle(
+ (($x + 0.5) * $this->scale),
+ (($y + 0.5) * $this->scale),
+ (($x + 0.5 + $this->circleRadius) * $this->scale),
+ (($y + 0.5) * $this->scale)
+ );
+
+ return;
+ }
+
+ $this->imagickDraw->rectangle(
+ ($x * $this->scale),
+ ($y * $this->scale),
+ ((($x + 1) * $this->scale) - 1),
+ ((($y + 1) * $this->scale) - 1)
+ );
}
}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php b/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php
index 06d6e88cb..240bd45ad 100644
--- a/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php
+++ b/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php
@@ -2,9 +2,7 @@
/**
* Class QRMarkup
*
- * @filesource QRMarkup.php
* @created 17.12.2016
- * @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2016 Smiley
* @license MIT
@@ -12,149 +10,85 @@
namespace chillerlan\QRCode\Output;
-use chillerlan\QRCode\QRCode;
-
-use function is_string, sprintf, strip_tags, trim;
+use function is_string, preg_match, strip_tags, trim;
/**
- * Converts the matrix into markup types: HTML, SVG, ...
+ * Abstract for markup types: HTML, SVG, ... XML anyone?
*/
-class QRMarkup extends QROutputAbstract{
-
- protected string $defaultMode = QRCode::OUTPUT_MARKUP_SVG;
-
- /**
- * @see \sprintf()
- */
- protected string $svgHeader = '<svg xmlns="http://www.w3.org/2000/svg" class="qr-svg %1$s" '.
- 'style="width: 100%%; height: auto;" viewBox="0 0 %2$d %2$d">';
+abstract class QRMarkup extends QROutputAbstract{
/**
+ * note: we're not necessarily validating the several values, just checking the general syntax
+ * note: css4 colors are not included
+ *
+ * @todo: XSS proof
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
* @inheritDoc
*/
- protected function setModuleValues():void{
-
- foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
- $v = $this->options->moduleValues[$M_TYPE] ?? null;
-
- if(!is_string($v)){
- $this->moduleValues[$M_TYPE] = $defaultValue
- ? $this->options->markupDark
- : $this->options->markupLight;
- }
- else{
- $this->moduleValues[$M_TYPE] = trim(strip_tags($v), '\'"');
- }
+ public static function moduleValueIsValid($value):bool{
+ if(!is_string($value)){
+ return false;
}
- }
-
- /**
- * HTML output
- */
- protected function html(string $file = null):string{
+ $value = trim(strip_tags($value), " '\"\r\n\t");
- $html = empty($this->options->cssClass)
- ? '<div>'
- : '<div class="'.$this->options->cssClass.'">';
-
- $html .= $this->options->eol;
-
- foreach($this->matrix->matrix() as $row){
- $html .= '<div>';
-
- foreach($row as $M_TYPE){
- $html .= '<span style="background: '.$this->moduleValues[$M_TYPE].';"></span>';
- }
-
- $html .= '</div>'.$this->options->eol;
+ // hex notation
+ // #rgb(a)
+ // #rrggbb(aa)
+ if(preg_match('/^#([\da-f]{3}){1,2}$|^#([\da-f]{4}){1,2}$/i', $value)){
+ return true;
}
- $html .= '</div>'.$this->options->eol;
+ // css: hsla/rgba(...values)
+ if(preg_match('#^(hsla?|rgba?)\([\d .,%/]+\)$#i', $value)){
+ return true;
+ }
- if($file !== null){
- return '<!DOCTYPE html>'.
- '<head><meta charset="UTF-8"><title>QR Code</title></head>'.
- '<body>'.$this->options->eol.$html.'</body>';
+ // predefined css color
+ if(preg_match('/^[a-z]+$/i', $value)){
+ return true;
}
- return $html;
+ return false;
}
/**
- * SVG output
- *
- * @see https://github.com/codemasher/php-qrcode/pull/5
+ * @inheritDoc
*/
- protected function svg(string $file = null):string{
- $matrix = $this->matrix->matrix();
-
- $svg = sprintf($this->svgHeader, $this->options->cssClass, $this->options->svgViewBoxSize ?? $this->moduleCount)
- .$this->options->eol
- .'<defs>'.$this->options->svgDefs.'</defs>'
- .$this->options->eol;
-
- foreach($this->moduleValues as $M_TYPE => $value){
- $path = '';
-
- foreach($matrix as $y => $row){
- //we'll combine active blocks within a single row as a lightweight compression technique
- $start = null;
- $count = 0;
-
- foreach($row as $x => $module){
-
- if($module === $M_TYPE){
- $count++;
-
- if($start === null){
- $start = $x;
- }
-
- if(isset($row[$x + 1])){
- continue;
- }
- }
-
- if($count > 0){
- $len = $count;
- $start ??= 0; // avoid type coercion in sprintf() - phan happy
-
- $path .= sprintf('M%s %s h%s v1 h-%sZ ', $start, $y, $len, $len);
-
- // reset count
- $count = 0;
- $start = null;
- }
-
- }
-
- }
-
- if(!empty($path)){
- $svg .= sprintf(
- '<path class="qr-%s %s" stroke="transparent" fill="%s" fill-opacity="%s" d="%s" />',
- $M_TYPE, $this->options->cssClass, $value, $this->options->svgOpacity, $path
- );
- }
+ protected function prepareModuleValue($value):string{
+ return trim(strip_tags($value), " '\"\r\n\t");
+ }
- }
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultModuleValue(bool $isDark):string{
+ return ($isDark) ? '#000' : '#fff';
+ }
- // close svg
- $svg .= '</svg>'.$this->options->eol;
+ /**
+ * @inheritDoc
+ */
+ public function dump(?string $file = null):string{
+ $data = $this->createMarkup($file !== null);
- // if saving to file, append the correct headers
- if($file !== null){
- return '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'.
- $this->options->eol.$svg;
- }
+ $this->saveToFile($data, $file);
- if($this->options->imageBase64){
- $svg = sprintf('data:image/svg+xml;base64,%s', base64_encode($svg));
- }
+ return $data;
+ }
- return $svg;
+ /**
+ * returns a string with all css classes for the current element
+ */
+ protected function getCssClass(int $M_TYPE = 0):string{
+ return $this->options->cssClass;
}
+ /**
+ * returns the fully parsed and rendered markup string for the given input
+ */
+ abstract protected function createMarkup(bool $saveToFile):string;
+
}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php b/vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php
new file mode 100644
index 000000000..65dc49a8a
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Output/QRMarkupHTML.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Class QRMarkupHTML
+ *
+ * @created 06.06.2022
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2022 smiley
+ * @license MIT
+ */
+
+namespace chillerlan\QRCode\Output;
+
+use function implode, sprintf;
+
+/**
+ * HTML output (a cheap markup substitute when SVG is not available or not an option)
+ */
+class QRMarkupHTML extends QRMarkup{
+
+ public const MIME_TYPE = 'text/html';
+
+ /**
+ * @inheritDoc
+ */
+ protected function createMarkup(bool $saveToFile):string{
+ $rows = [];
+ $cssClass = $this->getCssClass();
+
+ foreach($this->matrix->getMatrix() as $row){
+ $element = '<span style="background: %s;"></span>';
+ $modules = array_map(fn(int $M_TYPE):string => sprintf($element, $this->getModuleValue($M_TYPE)), $row);
+
+ $rows[] = sprintf('<div>%s</div>%s', implode('', $modules), $this->eol);
+ }
+
+ $html = sprintf('<div class="%1$s">%3$s%2$s</div>%3$s', $cssClass, implode('', $rows), $this->eol);
+
+ // wrap the snippet into a body when saving to file
+ if($saveToFile){
+ $html = sprintf(
+ '<!DOCTYPE html><html lang="none">%2$s<head>%2$s<meta charset="UTF-8">%2$s'.
+ '<title>QR Code</title></head>%2$s<body>%1$s</body>%2$s</html>',
+ $html,
+ $this->eol
+ );
+ }
+
+ return $html;
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php b/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php
new file mode 100644
index 000000000..735c4180b
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php
@@ -0,0 +1,200 @@
+<?php
+/**
+ * Class QRMarkupSVG
+ *
+ * @created 06.06.2022
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2022 smiley
+ * @license MIT
+ */
+
+namespace chillerlan\QRCode\Output;
+
+use function array_chunk, implode, is_string, preg_match, sprintf, trim;
+
+/**
+ * SVG output
+ *
+ * @see https://github.com/codemasher/php-qrcode/pull/5
+ * @see https://developer.mozilla.org/en-US/docs/Web/SVG
+ * @see https://www.sarasoueidan.com/demos/interactive-svg-coordinate-system/
+ * @see https://lea.verou.me/blog/2019/05/utility-convert-svg-path-to-all-relative-or-all-absolute-commands/
+ * @see https://codepen.io/leaverou/full/RmwzKv
+ * @see https://jakearchibald.github.io/svgomg/
+ * @see https://web.archive.org/web/20200220211445/http://apex.infogridpacific.com/SVG/svg-tutorial-contents.html
+ */
+class QRMarkupSVG extends QRMarkup{
+
+ public const MIME_TYPE = 'image/svg+xml';
+
+ /**
+ * @todo: XSS proof
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill
+ * @inheritDoc
+ */
+ public static function moduleValueIsValid($value):bool{
+
+ if(!is_string($value)){
+ return false;
+ }
+
+ $value = trim($value);
+
+ // url(...)
+ if(preg_match('~^url\([-/#a-z\d]+\)$~i', $value)){
+ return true;
+ }
+
+ // otherwise check for standard css notation
+ return parent::moduleValueIsValid($value);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getOutputDimensions():array{
+ return [$this->moduleCount, $this->moduleCount];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getCssClass(int $M_TYPE = 0):string{
+ return implode(' ', [
+ 'qr-'.($this::LAYERNAMES[$M_TYPE] ?? $M_TYPE),
+ $this->matrix->isDark($M_TYPE) ? 'dark' : 'light',
+ $this->options->cssClass,
+ ]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function createMarkup(bool $saveToFile):string{
+ $svg = $this->header();
+
+ if(!empty($this->options->svgDefs)){
+ $svg .= sprintf('<defs>%1$s%2$s</defs>%2$s', $this->options->svgDefs, $this->eol);
+ }
+
+ $svg .= $this->paths();
+
+ // close svg
+ $svg .= sprintf('%1$s</svg>%1$s', $this->eol);
+
+ // transform to data URI only when not saving to file
+ if(!$saveToFile && $this->options->outputBase64){
+ $svg = $this->toBase64DataURI($svg);
+ }
+
+ return $svg;
+ }
+
+ /**
+ * returns the value for the SVG viewBox attribute
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox
+ * @see https://css-tricks.com/scale-svg/#article-header-id-3
+ */
+ protected function getViewBox():string{
+ [$width, $height] = $this->getOutputDimensions();
+
+ return sprintf('0 0 %s %s', $width, $height);
+ }
+
+ /**
+ * returns the <svg> header with the given options parsed
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg
+ */
+ protected function header():string{
+
+ $header = sprintf(
+ '<svg xmlns="http://www.w3.org/2000/svg" class="qr-svg %1$s" viewBox="%2$s" preserveAspectRatio="%3$s">%4$s',
+ $this->options->cssClass,
+ $this->getViewBox(),
+ $this->options->svgPreserveAspectRatio,
+ $this->eol
+ );
+
+ if($this->options->svgAddXmlHeader){
+ $header = sprintf('<?xml version="1.0" encoding="UTF-8"?>%s%s', $this->eol, $header);
+ }
+
+ return $header;
+ }
+
+ /**
+ * returns one or more SVG <path> elements
+ */
+ protected function paths():string{
+ $paths = $this->collectModules(fn(int $x, int $y, int $M_TYPE):string => $this->module($x, $y, $M_TYPE));
+ $svg = [];
+
+ // create the path elements
+ foreach($paths as $M_TYPE => $modules){
+ // limit the total line length
+ $chunks = array_chunk($modules, 100);
+ $chonks = [];
+
+ foreach($chunks as $chunk){
+ $chonks[] = implode(' ', $chunk);
+ }
+
+ $path = implode($this->eol, $chonks);
+
+ if(empty($path)){
+ continue;
+ }
+
+ $svg[] = $this->path($path, $M_TYPE);
+ }
+
+ return implode($this->eol, $svg);
+ }
+
+ /**
+ * renders and returns a single <path> element
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
+ */
+ protected function path(string $path, int $M_TYPE):string{
+
+ if($this->options->svgUseFillAttributes){
+ return sprintf(
+ '<path class="%s" fill="%s" d="%s"/>',
+ $this->getCssClass($M_TYPE),
+ $this->getModuleValue($M_TYPE),
+ $path
+ );
+ }
+
+ return sprintf('<path class="%s" d="%s"/>', $this->getCssClass($M_TYPE), $path);
+ }
+
+ /**
+ * returns a path segment for a single module
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
+ */
+ protected function module(int $x, int $y, int $M_TYPE):string{
+
+ if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){
+ return '';
+ }
+
+ if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){
+ // string interpolation: ugly and fast
+ $ix = ($x + 0.5 - $this->circleRadius);
+ $iy = ($y + 0.5);
+
+ // phpcs:ignore
+ return "M$ix $iy a$this->circleRadius $this->circleRadius 0 1 0 $this->circleDiameter 0 a$this->circleRadius $this->circleRadius 0 1 0 -$this->circleDiameter 0Z";
+ }
+
+ // phpcs:ignore
+ return "M$x $y h1 v1 h-1Z";
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php b/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php
index d4ed3d0c9..a2757ac8c 100644
--- a/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php
+++ b/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php
@@ -2,9 +2,7 @@
/**
* Class QROutputAbstract
*
- * @filesource QROutputAbstract.php
* @created 09.12.2015
- * @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
@@ -12,10 +10,10 @@
namespace chillerlan\QRCode\Output;
-use chillerlan\QRCode\{Data\QRMatrix, QRCode};
+use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\Settings\SettingsContainerInterface;
-
-use function call_user_func_array, dirname, file_put_contents, get_called_class, in_array, is_writable, sprintf;
+use Closure;
+use function base64_encode, dirname, file_put_contents, is_writable, ksort, sprintf;
/**
* common output abstract
@@ -25,30 +23,11 @@ abstract class QROutputAbstract implements QROutputInterface{
/**
* the current size of the QR matrix
*
- * @see \chillerlan\QRCode\Data\QRMatrix::size()
+ * @see \chillerlan\QRCode\Data\QRMatrix::getSize()
*/
protected int $moduleCount;
/**
- * the current output mode
- *
- * @see \chillerlan\QRCode\QROptions::$outputType
- */
- protected string $outputMode;
-
- /**
- * the default output mode of the current output module
- */
- protected string $defaultMode;
-
- /**
- * the current scaling for a QR pixel
- *
- * @see \chillerlan\QRCode\QROptions::$scale
- */
- protected int $scale;
-
- /**
* the side length of the QR image (modules * scale)
*/
protected int $length;
@@ -68,62 +47,215 @@ abstract class QROutputAbstract implements QROutputInterface{
*/
protected SettingsContainerInterface $options;
+ /** @see \chillerlan\QRCode\QROptions::$scale */
+ protected int $scale;
+ /** @see \chillerlan\QRCode\QROptions::$connectPaths */
+ protected bool $connectPaths;
+ /** @see \chillerlan\QRCode\QROptions::$excludeFromConnect */
+ protected array $excludeFromConnect;
+ /** @see \chillerlan\QRCode\QROptions::$eol */
+ protected string $eol;
+ /** @see \chillerlan\QRCode\QROptions::$drawLightModules */
+ protected bool $drawLightModules;
+ /** @see \chillerlan\QRCode\QROptions::$drawCircularModules */
+ protected bool $drawCircularModules;
+ /** @see \chillerlan\QRCode\QROptions::$keepAsSquare */
+ protected array $keepAsSquare;
+ /** @see \chillerlan\QRCode\QROptions::$circleRadius */
+ protected float $circleRadius;
+ protected float $circleDiameter;
+
/**
* QROutputAbstract constructor.
*/
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
- $this->options = $options;
- $this->matrix = $matrix;
- $this->moduleCount = $this->matrix->size();
+ $this->options = $options;
+ $this->matrix = $matrix;
+
+ if($this->options->invertMatrix){
+ $this->matrix->invert();
+ }
+
+ $this->copyVars();
+ $this->setMatrixDimensions();
+ $this->setModuleValues();
+ }
+
+ /**
+ * Creates copies of several QROptions values to avoid calling the magic getters
+ * in long loops for a significant performance increase.
+ *
+ * These variables are usually used in the "module" methods and are called up to 31329 times (at version 40).
+ */
+ protected function copyVars():void{
+
+ $vars = [
+ 'connectPaths',
+ 'excludeFromConnect',
+ 'eol',
+ 'drawLightModules',
+ 'drawCircularModules',
+ 'keepAsSquare',
+ 'circleRadius',
+ ];
+
+ foreach($vars as $property){
+ $this->{$property} = $this->options->{$property};
+ }
+
+ $this->circleDiameter = ($this->circleRadius * 2);
+ }
+
+ /**
+ * Sets/updates the matrix dimensions
+ *
+ * Call this method if you modify the matrix from within your custom module in case the dimensions have been changed
+ */
+ protected function setMatrixDimensions():void{
+ $this->moduleCount = $this->matrix->getSize();
$this->scale = $this->options->scale;
- $this->length = $this->moduleCount * $this->scale;
+ $this->length = ($this->moduleCount * $this->scale);
+ }
+
+ /**
+ * Returns a 2 element array with the current output width and height
+ *
+ * The type and units of the values depend on the output class. The default value is the current module count * scale.
+ */
+ protected function getOutputDimensions():array{
+ return [$this->length, $this->length];
+ }
- $class = get_called_class();
+ /**
+ * Sets the initial module values
+ */
+ protected function setModuleValues():void{
- if(isset(QRCode::OUTPUT_MODES[$class]) && in_array($this->options->outputType, QRCode::OUTPUT_MODES[$class])){
- $this->outputMode = $this->options->outputType;
+ // first fill the map with the default values
+ foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
+ $this->moduleValues[$M_TYPE] = $this->getDefaultModuleValue($defaultValue);
}
- $this->setModuleValues();
+ // now loop over the options values to replace defaults and add extra values
+ foreach($this->options->moduleValues as $M_TYPE => $value){
+ if($this::moduleValueIsValid($value)){
+ $this->moduleValues[$M_TYPE] = $this->prepareModuleValue($value);
+ }
+ }
+
+ }
+
+ /**
+ * Prepares the value for the given input (return value depends on the output class)
+ *
+ * @param mixed $value
+ *
+ * @return mixed|null
+ */
+ abstract protected function prepareModuleValue($value);
+
+ /**
+ * Returns a default value for either dark or light modules (return value depends on the output class)
+ *
+ * @return mixed|null
+ */
+ abstract protected function getDefaultModuleValue(bool $isDark);
+
+ /**
+ * Returns the prepared value for the given $M_TYPE
+ *
+ * @return mixed return value depends on the output class
+ * @throws \chillerlan\QRCode\Output\QRCodeOutputException if $moduleValues[$M_TYPE] doesn't exist
+ */
+ protected function getModuleValue(int $M_TYPE){
+
+ if(!isset($this->moduleValues[$M_TYPE])){
+ throw new QRCodeOutputException(sprintf('$M_TYPE %012b not found in module values map', $M_TYPE));
+ }
+
+ return $this->moduleValues[$M_TYPE];
+ }
+
+ /**
+ * Returns the prepared module value at the given coordinate [$x, $y] (convenience)
+ *
+ * @return mixed|null
+ */
+ protected function getModuleValueAt(int $x, int $y){
+ return $this->getModuleValue($this->matrix->get($x, $y));
}
/**
- * Sets the initial module values (clean-up & defaults)
+ * Returns a base64 data URI for the given string and mime type
*/
- abstract protected function setModuleValues():void;
+ protected function toBase64DataURI(string $data, ?string $mime = null):string{
+ return sprintf('data:%s;base64,%s', ($mime ?? $this::MIME_TYPE), base64_encode($data));
+ }
/**
- * saves the qr data to a file
+ * Saves the qr $data to a $file. If $file is null, nothing happens.
*
* @see file_put_contents()
- * @see \chillerlan\QRCode\QROptions::cachefile
+ * @see \chillerlan\QRCode\QROptions::$cachefile
*
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
*/
- protected function saveToFile(string $data, string $file):bool{
+ protected function saveToFile(string $data, ?string $file = null):void{
+
+ if($file === null){
+ return;
+ }
if(!is_writable(dirname($file))){
- throw new QRCodeOutputException(sprintf('Could not write data to cache file: %s', $file));
+ throw new QRCodeOutputException(sprintf('Cannot write data to cache file: %s', $file));
}
- return (bool)file_put_contents($file, $data);
+ if(file_put_contents($file, $data) === false){
+ throw new QRCodeOutputException(sprintf('Cannot write data to cache file: %s (file_put_contents error)', $file));
+ }
}
/**
- * @inheritDoc
+ * collects the modules per QRMatrix::M_* type and runs a $transform function on each module and
+ * returns an array with the transformed modules
+ *
+ * The transform callback is called with the following parameters:
+ *
+ * $x - current column
+ * $y - current row
+ * $M_TYPE - field value
+ * $M_TYPE_LAYER - (possibly modified) field value that acts as layer id
*/
- public function dump(string $file = null){
- $file ??= $this->options->cachefile;
+ protected function collectModules(Closure $transform):array{
+ $paths = [];
- // call the built-in output method with the optional file path as parameter
- // to make the called method aware if a cache file was given
- $data = call_user_func_array([$this, $this->outputMode ?? $this->defaultMode], [$file]);
+ // collect the modules for each type
+ foreach($this->matrix->getMatrix() as $y => $row){
+ foreach($row as $x => $M_TYPE){
+ $M_TYPE_LAYER = $M_TYPE;
- if($file !== null){
- $this->saveToFile($data, $file);
+ if($this->connectPaths && !$this->matrix->checkTypeIn($x, $y, $this->excludeFromConnect)){
+ // to connect paths we'll redeclare the $M_TYPE_LAYER to data only
+ $M_TYPE_LAYER = QRMatrix::M_DATA;
+
+ if($this->matrix->isDark($M_TYPE)){
+ $M_TYPE_LAYER = QRMatrix::M_DATA_DARK;
+ }
+ }
+
+ // collect the modules per $M_TYPE
+ $module = $transform($x, $y, $M_TYPE, $M_TYPE_LAYER);
+
+ if(!empty($module)){
+ $paths[$M_TYPE_LAYER][] = $module;
+ }
+ }
}
- return $data;
+ // beautify output
+ ksort($paths);
+
+ return $paths;
}
}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php b/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php
index b07b8e7a5..7d2315180 100644
--- a/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php
+++ b/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php
@@ -2,9 +2,7 @@
/**
* Interface QROutputInterface,
*
- * @filesource QROutputInterface.php
* @created 02.12.2015
- * @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
@@ -19,36 +17,210 @@ use chillerlan\QRCode\Data\QRMatrix;
*/
interface QROutputInterface{
- const DEFAULT_MODULE_VALUES = [
+ /**
+ * @var string
+ * @deprecated 5.0.0 <no replacement>
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ */
+ public const MARKUP_HTML = 'html';
+
+ /**
+ * @var string
+ * @deprecated 5.0.0 <no replacement>
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ */
+ public const MARKUP_SVG = 'svg';
+
+ /**
+ * @var string
+ * @deprecated 5.0.0 <no replacement>
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ */
+ public const GDIMAGE_BMP = 'bmp';
+
+ /**
+ * @var string
+ * @deprecated 5.0.0 <no replacement>
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ */
+ public const GDIMAGE_GIF = 'gif';
+
+ /**
+ * @var string
+ * @deprecated 5.0.0 <no replacement>
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ */
+ public const GDIMAGE_JPG = 'jpg';
+
+ /**
+ * @var string
+ * @deprecated 5.0.0 <no replacement>
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ */
+ public const GDIMAGE_PNG = 'png';
+
+ /**
+ * @var string
+ * @deprecated 5.0.0 <no replacement>
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ */
+ public const GDIMAGE_WEBP = 'webp';
+
+ /**
+ * @var string
+ * @deprecated 5.0.0 <no replacement>
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ */
+ public const STRING_JSON = 'json';
+
+ /**
+ * @var string
+ * @deprecated 5.0.0 <no replacement>
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ */
+ public const STRING_TEXT = 'text';
+
+ /**
+ * @var string
+ * @deprecated 5.0.0 <no replacement>
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ */
+ public const IMAGICK = 'imagick';
+
+ /**
+ * @var string
+ * @deprecated 5.0.0 <no replacement>
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ */
+ public const FPDF = 'fpdf';
+
+ /**
+ * @var string
+ * @deprecated 5.0.0 <no replacement>
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ */
+ public const EPS = 'eps';
+
+ /**
+ * @var string
+ * @deprecated 5.0.0 <no replacement>
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ */
+ public const CUSTOM = 'custom';
+
+ /**
+ * Map of built-in output modes => class FQN
+ *
+ * @var string[]
+ * @deprecated 5.0.0 <no replacement>
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ */
+ public const MODES = [
+ self::MARKUP_SVG => QRMarkupSVG::class,
+ self::MARKUP_HTML => QRMarkupHTML::class,
+ self::GDIMAGE_BMP => QRGdImageBMP::class,
+ self::GDIMAGE_GIF => QRGdImageGIF::class,
+ self::GDIMAGE_JPG => QRGdImageJPEG::class,
+ self::GDIMAGE_PNG => QRGdImagePNG::class,
+ self::GDIMAGE_WEBP => QRGdImageWEBP::class,
+ self::STRING_JSON => QRStringJSON::class,
+ self::STRING_TEXT => QRStringText::class,
+ self::IMAGICK => QRImagick::class,
+ self::FPDF => QRFpdf::class,
+ self::EPS => QREps::class,
+ ];
+
+ /**
+ * Map of module type => default value
+ *
+ * @var bool[]
+ */
+ public const DEFAULT_MODULE_VALUES = [
// light
- QRMatrix::M_NULL => false, // 0
- QRMatrix::M_DATA => false, // 4
- QRMatrix::M_FINDER => false, // 6
- QRMatrix::M_SEPARATOR => false, // 8
- QRMatrix::M_ALIGNMENT => false, // 10
- QRMatrix::M_TIMING => false, // 12
- QRMatrix::M_FORMAT => false, // 14
- QRMatrix::M_VERSION => false, // 16
- QRMatrix::M_QUIETZONE => false, // 18
- QRMatrix::M_LOGO => false, // 20
- QRMatrix::M_TEST => false, // 255
+ QRMatrix::M_NULL => false,
+ QRMatrix::M_DARKMODULE_LIGHT => false,
+ QRMatrix::M_DATA => false,
+ QRMatrix::M_FINDER => false,
+ QRMatrix::M_SEPARATOR => false,
+ QRMatrix::M_ALIGNMENT => false,
+ QRMatrix::M_TIMING => false,
+ QRMatrix::M_FORMAT => false,
+ QRMatrix::M_VERSION => false,
+ QRMatrix::M_QUIETZONE => false,
+ QRMatrix::M_LOGO => false,
+ QRMatrix::M_FINDER_DOT_LIGHT => false,
// dark
- QRMatrix::M_DARKMODULE << 8 => true, // 512
- QRMatrix::M_DATA << 8 => true, // 1024
- QRMatrix::M_FINDER << 8 => true, // 1536
- QRMatrix::M_ALIGNMENT << 8 => true, // 2560
- QRMatrix::M_TIMING << 8 => true, // 3072
- QRMatrix::M_FORMAT << 8 => true, // 3584
- QRMatrix::M_VERSION << 8 => true, // 4096
- QRMatrix::M_FINDER_DOT << 8 => true, // 5632
- QRMatrix::M_TEST << 8 => true, // 65280
+ QRMatrix::M_DARKMODULE => true,
+ QRMatrix::M_DATA_DARK => true,
+ QRMatrix::M_FINDER_DARK => true,
+ QRMatrix::M_SEPARATOR_DARK => true,
+ QRMatrix::M_ALIGNMENT_DARK => true,
+ QRMatrix::M_TIMING_DARK => true,
+ QRMatrix::M_FORMAT_DARK => true,
+ QRMatrix::M_VERSION_DARK => true,
+ QRMatrix::M_QUIETZONE_DARK => true,
+ QRMatrix::M_LOGO_DARK => true,
+ QRMatrix::M_FINDER_DOT => true,
];
/**
- * generates the output, optionally dumps it to a file, and returns it
+ * Map of module type => readable name (for CSS etc.)
+ *
+ * @var string[]
+ */
+ public const LAYERNAMES = [
+ // light
+ QRMatrix::M_NULL => 'null',
+ QRMatrix::M_DARKMODULE_LIGHT => 'darkmodule-light',
+ QRMatrix::M_DATA => 'data',
+ QRMatrix::M_FINDER => 'finder',
+ QRMatrix::M_SEPARATOR => 'separator',
+ QRMatrix::M_ALIGNMENT => 'alignment',
+ QRMatrix::M_TIMING => 'timing',
+ QRMatrix::M_FORMAT => 'format',
+ QRMatrix::M_VERSION => 'version',
+ QRMatrix::M_QUIETZONE => 'quietzone',
+ QRMatrix::M_LOGO => 'logo',
+ QRMatrix::M_FINDER_DOT_LIGHT => 'finder-dot-light',
+ // dark
+ QRMatrix::M_DARKMODULE => 'darkmodule',
+ QRMatrix::M_DATA_DARK => 'data-dark',
+ QRMatrix::M_FINDER_DARK => 'finder-dark',
+ QRMatrix::M_SEPARATOR_DARK => 'separator-dark',
+ QRMatrix::M_ALIGNMENT_DARK => 'alignment-dark',
+ QRMatrix::M_TIMING_DARK => 'timing-dark',
+ QRMatrix::M_FORMAT_DARK => 'format-dark',
+ QRMatrix::M_VERSION_DARK => 'version-dark',
+ QRMatrix::M_QUIETZONE_DARK => 'quietzone-dark',
+ QRMatrix::M_LOGO_DARK => 'logo-dark',
+ QRMatrix::M_FINDER_DOT => 'finder-dot',
+ ];
+
+ /**
+ * @var string
+ * @see \chillerlan\QRCode\Output\QROutputAbstract::toBase64DataURI()
+ * @internal do not call this constant from the interface, but rather from one of the child classes
+ */
+ public const MIME_TYPE = '';
+
+ /**
+ * Determines whether the given value is valid
+ *
+ * @param mixed $value
+ */
+ public static function moduleValueIsValid($value):bool;
+
+ /**
+ * Generates the output, optionally dumps it to a file, and returns it
+ *
+ * please note that the value of QROptions::$cachefile is already evaluated at this point.
+ * if the output module is invoked manually, it has no effect at all.
+ * you need to supply the $file parameter here in that case (or handle the option value in your custom output module).
+ *
+ * @see \chillerlan\QRCode\QRCode::renderMatrix()
*
* @return mixed
*/
- public function dump(string $file = null);
+ public function dump(?string $file = null);
}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRString.php b/vendor/chillerlan/php-qrcode/src/Output/QRString.php
index 3ed5153e1..2d6d052d6 100644
--- a/vendor/chillerlan/php-qrcode/src/Output/QRString.php
+++ b/vendor/chillerlan/php-qrcode/src/Output/QRString.php
@@ -2,75 +2,110 @@
/**
* Class QRString
*
- * @filesource QRString.php
* @created 05.12.2015
- * @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*
- * @noinspection PhpUnusedParameterInspection
* @noinspection PhpComposerExtensionStubsInspection
*/
namespace chillerlan\QRCode\Output;
-use chillerlan\QRCode\QRCode;
-
-use function implode, is_string, json_encode;
+use function implode, is_string, json_encode, max, min, sprintf;
+use const JSON_THROW_ON_ERROR;
/**
* Converts the matrix data into string types
+ *
+ * @deprecated 5.0.0 this class will be removed in future versions, use one of QRStringText or QRStringJSON instead
*/
class QRString extends QROutputAbstract{
- protected string $defaultMode = QRCode::OUTPUT_STRING_TEXT;
-
/**
* @inheritDoc
*/
- protected function setModuleValues():void{
+ public static function moduleValueIsValid($value):bool{
+ return is_string($value);
+ }
- foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
- $v = $this->options->moduleValues[$M_TYPE] ?? null;
+ /**
+ * @inheritDoc
+ */
+ protected function prepareModuleValue($value):string{
+ return $value;
+ }
- if(!is_string($v)){
- $this->moduleValues[$M_TYPE] = $defaultValue
- ? $this->options->textDark
- : $this->options->textLight;
- }
- else{
- $this->moduleValues[$M_TYPE] = $v;
- }
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultModuleValue(bool $isDark):string{
+ return ($isDark) ? '██' : '░░';
+ }
+ /**
+ * @inheritDoc
+ */
+ public function dump(?string $file = null):string{
+
+ switch($this->options->outputType){
+ case QROutputInterface::STRING_TEXT:
+ $data = $this->text();
+ break;
+ case QROutputInterface::STRING_JSON:
+ default:
+ $data = $this->json();
}
+ $this->saveToFile($data, $file);
+
+ return $data;
}
/**
* string output
*/
- protected function text(string $file = null):string{
- $str = [];
+ protected function text():string{
+ $lines = [];
+ $linestart = $this->options->textLineStart;
- foreach($this->matrix->matrix() as $row){
+ for($y = 0; $y < $this->moduleCount; $y++){
$r = [];
- foreach($row as $M_TYPE){
- $r[] = $this->moduleValues[$M_TYPE];
+ for($x = 0; $x < $this->moduleCount; $x++){
+ $r[] = $this->getModuleValueAt($x, $y);
}
- $str[] = implode('', $r);
+ $lines[] = $linestart.implode('', $r);
}
- return implode($this->options->eol, $str);
+ return implode($this->eol, $lines);
}
/**
* JSON output
+ *
+ * @throws \JsonException
*/
- protected function json(string $file = null):string{
- return json_encode($this->matrix->matrix());
+ protected function json():string{
+ return json_encode($this->matrix->getMatrix($this->options->jsonAsBooleans), JSON_THROW_ON_ERROR);
+ }
+
+ //
+
+ /**
+ * a little helper to create a proper ANSI 8-bit color escape sequence
+ *
+ * @see https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
+ * @see https://en.wikipedia.org/wiki/Block_Elements
+ *
+ * @codeCoverageIgnore
+ */
+ public static function ansi8(string $str, int $color, ?bool $background = null):string{
+ $color = max(0, min($color, 255));
+ $background = ($background === true) ? 48 : 38;
+
+ return sprintf("\x1b[%s;5;%sm%s\x1b[0m", $background, $color, $str);
}
}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php b/vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php
new file mode 100644
index 000000000..87ed2d7ff
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Output/QRStringJSON.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Class QRStringJSON
+ *
+ * @created 25.10.2023
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2023 smiley
+ * @license MIT
+ *
+ * @noinspection PhpComposerExtensionStubsInspection
+ */
+
+namespace chillerlan\QRCode\Output;
+
+use function json_encode;
+
+/**
+ *
+ */
+class QRStringJSON extends QROutputAbstract{
+
+ public const MIME_TYPE = 'application/json';
+
+ /**
+ * @inheritDoc
+ * @throws \JsonException
+ */
+ public function dump(?string $file = null):string{
+ $matrix = $this->matrix->getMatrix($this->options->jsonAsBooleans);
+ $data = json_encode($matrix, $this->options->jsonFlags);
+
+ $this->saveToFile($data, $file);
+
+ return $data;
+ }
+
+ /**
+ * unused - required by interface
+ *
+ * @inheritDoc
+ * @codeCoverageIgnore
+ */
+ protected function prepareModuleValue($value):string{
+ return '';
+ }
+
+ /**
+ * unused - required by interface
+ *
+ * @inheritDoc
+ * @codeCoverageIgnore
+ */
+ protected function getDefaultModuleValue(bool $isDark):string{
+ return '';
+ }
+
+ /**
+ * unused - required by interface
+ *
+ * @inheritDoc
+ * @codeCoverageIgnore
+ */
+ public static function moduleValueIsValid($value):bool{
+ return true;
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRStringText.php b/vendor/chillerlan/php-qrcode/src/Output/QRStringText.php
new file mode 100644
index 000000000..a91591da7
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Output/QRStringText.php
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Class QRStringText
+ *
+ * @created 25.10.2023
+ * @author smiley <smiley@chillerlan.net>
+ * @copyright 2023 smiley
+ * @license MIT
+ */
+
+namespace chillerlan\QRCode\Output;
+
+use function array_map, implode, is_string, max, min, sprintf;
+
+/**
+ *
+ */
+class QRStringText extends QROutputAbstract{
+
+ public const MIME_TYPE = 'text/plain';
+
+ /**
+ * @inheritDoc
+ */
+ public static function moduleValueIsValid($value):bool{
+ return is_string($value);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function prepareModuleValue($value):string{
+ return $value;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultModuleValue(bool $isDark):string{
+ return ($isDark) ? '██' : '░░';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function dump(?string $file = null):string{
+ $lines = [];
+ $linestart = $this->options->textLineStart;
+
+ foreach($this->matrix->getMatrix() as $row){
+ $lines[] = $linestart.implode('', array_map([$this, 'getModuleValue'], $row));
+ }
+
+ $data = implode($this->eol, $lines);
+
+ $this->saveToFile($data, $file);
+
+ return $data;
+ }
+
+ /**
+ * a little helper to create a proper ANSI 8-bit color escape sequence
+ *
+ * @see https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
+ * @see https://en.wikipedia.org/wiki/Block_Elements
+ *
+ * @codeCoverageIgnore
+ */
+ public static function ansi8(string $str, int $color, ?bool $background = null):string{
+ $color = max(0, min($color, 255));
+ $background = ($background === true) ? 48 : 38;
+
+ return sprintf("\x1b[%s;5;%sm%s\x1b[0m", $background, $color, $str);
+ }
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/QRCode.php b/vendor/chillerlan/php-qrcode/src/QRCode.php
index 908030feb..235cb06d6 100755
--- a/vendor/chillerlan/php-qrcode/src/QRCode.php
+++ b/vendor/chillerlan/php-qrcode/src/QRCode.php
@@ -2,153 +2,161 @@
/**
* Class QRCode
*
- * @filesource QRCode.php
* @created 26.11.2015
- * @package chillerlan\QRCode
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
namespace chillerlan\QRCode;
-use chillerlan\QRCode\Data\{
- AlphaNum, Byte, Kanji, MaskPatternTester, Number, QRCodeDataException, QRDataInterface, QRMatrix
-};
-use chillerlan\QRCode\Output\{
- QRCodeOutputException, QRFpdf, QRImage, QRImagick, QRMarkup, QROutputInterface, QRString
+use chillerlan\QRCode\Common\{
+ EccLevel, ECICharset, GDLuminanceSource, IMagickLuminanceSource, LuminanceSourceInterface, MaskPattern, Mode, Version
};
+use chillerlan\QRCode\Data\{AlphaNum, Byte, ECI, Hanzi, Kanji, Number, QRData, QRDataModeInterface, QRMatrix};
+use chillerlan\QRCode\Decoder\{Decoder, DecoderResult};
+use chillerlan\QRCode\Output\{QRCodeOutputException, QROutputInterface};
use chillerlan\Settings\SettingsContainerInterface;
-
-use function call_user_func_array, class_exists, in_array, ord, strlen, strtolower, str_split;
+use function class_exists, class_implements, in_array, mb_convert_encoding, mb_internal_encoding;
/**
* Turns a text string into a Model 2 QR Code
*
* @see https://github.com/kazuhikoarase/qrcode-generator/tree/master/php
- * @see http://www.qrcode.com/en/codes/model12.html
+ * @see https://www.qrcode.com/en/codes/model12.html
* @see https://www.swisseduc.ch/informatik/theoretische_informatik/qr_codes/docs/qr_standard.pdf
* @see https://en.wikipedia.org/wiki/QR_code
- * @see http://www.thonky.com/qr-code-tutorial/
+ * @see https://www.thonky.com/qr-code-tutorial/
*/
class QRCode{
- /** @var int */
- public const VERSION_AUTO = -1;
- /** @var int */
- public const MASK_PATTERN_AUTO = -1;
+ /**
+ * @deprecated 5.0.0 use Version::AUTO instead
+ * @see \chillerlan\QRCode\Common\Version::AUTO
+ * @var int
+ */
+ public const VERSION_AUTO = Version::AUTO;
- // ISO/IEC 18004:2000 Table 2
+ /**
+ * @deprecated 5.0.0 use MaskPattern::AUTO instead
+ * @see \chillerlan\QRCode\Common\MaskPattern::AUTO
+ * @var int
+ */
+ public const MASK_PATTERN_AUTO = MaskPattern::AUTO;
- /** @var int */
- public const DATA_NUMBER = 0b0001;
- /** @var int */
- public const DATA_ALPHANUM = 0b0010;
- /** @var int */
- public const DATA_BYTE = 0b0100;
- /** @var int */
- public const DATA_KANJI = 0b1000;
+ /**
+ * @deprecated 5.0.0 use EccLevel::L instead
+ * @see \chillerlan\QRCode\Common\EccLevel::L
+ * @var int
+ */
+ public const ECC_L = EccLevel::L;
/**
- * References to the keys of the following tables:
- *
- * @see \chillerlan\QRCode\Data\QRDataInterface::MAX_LENGTH
- *
- * @var int[]
+ * @deprecated 5.0.0 use EccLevel::M instead
+ * @see \chillerlan\QRCode\Common\EccLevel::M
+ * @var int
*/
- public const DATA_MODES = [
- self::DATA_NUMBER => 0,
- self::DATA_ALPHANUM => 1,
- self::DATA_BYTE => 2,
- self::DATA_KANJI => 3,
- ];
+ public const ECC_M = EccLevel::M;
- // ISO/IEC 18004:2000 Tables 12, 25
+ /**
+ * @deprecated 5.0.0 use EccLevel::Q instead
+ * @see \chillerlan\QRCode\Common\EccLevel::Q
+ * @var int
+ */
+ public const ECC_Q = EccLevel::Q;
- /** @var int */
- public const ECC_L = 0b01; // 7%.
- /** @var int */
- public const ECC_M = 0b00; // 15%.
- /** @var int */
- public const ECC_Q = 0b11; // 25%.
- /** @var int */
- public const ECC_H = 0b10; // 30%.
+ /**
+ * @deprecated 5.0.0 use EccLevel::H instead
+ * @see \chillerlan\QRCode\Common\EccLevel::H
+ * @var int
+ */
+ public const ECC_H = EccLevel::H;
/**
- * References to the keys of the following tables:
- *
- * @see \chillerlan\QRCode\Data\QRDataInterface::MAX_BITS
- * @see \chillerlan\QRCode\Data\QRDataInterface::RSBLOCKS
- * @see \chillerlan\QRCode\Data\QRMatrix::formatPattern
- *
- * @var int[]
- */
- public const ECC_MODES = [
- self::ECC_L => 0,
- self::ECC_M => 1,
- self::ECC_Q => 2,
- self::ECC_H => 3,
- ];
-
- /** @var string */
- public const OUTPUT_MARKUP_HTML = 'html';
- /** @var string */
- public const OUTPUT_MARKUP_SVG = 'svg';
- /** @var string */
- public const OUTPUT_IMAGE_PNG = 'png';
- /** @var string */
- public const OUTPUT_IMAGE_JPG = 'jpg';
- /** @var string */
- public const OUTPUT_IMAGE_GIF = 'gif';
- /** @var string */
- public const OUTPUT_STRING_JSON = 'json';
- /** @var string */
- public const OUTPUT_STRING_TEXT = 'text';
- /** @var string */
- public const OUTPUT_IMAGICK = 'imagick';
- /** @var string */
- public const OUTPUT_FPDF = 'fpdf';
- /** @var string */
- public const OUTPUT_CUSTOM = 'custom';
-
- /**
- * Map of built-in output modules => capabilities
- *
- * @var string[][]
- */
- public const OUTPUT_MODES = [
- QRMarkup::class => [
- self::OUTPUT_MARKUP_SVG,
- self::OUTPUT_MARKUP_HTML,
- ],
- QRImage::class => [
- self::OUTPUT_IMAGE_PNG,
- self::OUTPUT_IMAGE_GIF,
- self::OUTPUT_IMAGE_JPG,
- ],
- QRString::class => [
- self::OUTPUT_STRING_JSON,
- self::OUTPUT_STRING_TEXT,
- ],
- QRImagick::class => [
- self::OUTPUT_IMAGICK,
- ],
- QRFpdf::class => [
- self::OUTPUT_FPDF
- ]
- ];
-
- /**
- * Map of data mode => interface
- *
+ * @deprecated 5.0.0 use QROutputInterface::MARKUP_HTML instead
+ * @see \chillerlan\QRCode\Output\QROutputInterface::MARKUP_HTML
+ * @var string
+ */
+ public const OUTPUT_MARKUP_HTML = QROutputInterface::MARKUP_HTML;
+
+ /**
+ * @deprecated 5.0.0 use QROutputInterface::MARKUP_SVG instead
+ * @see \chillerlan\QRCode\Output\QROutputInterface::MARKUP_SVG
+ * @var string
+ */
+ public const OUTPUT_MARKUP_SVG = QROutputInterface::MARKUP_SVG;
+
+ /**
+ * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_PNG instead
+ * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_PNG
+ * @var string
+ */
+ public const OUTPUT_IMAGE_PNG = QROutputInterface::GDIMAGE_PNG;
+
+ /**
+ * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_JPG instead
+ * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_JPG
+ * @var string
+ */
+ public const OUTPUT_IMAGE_JPG = QROutputInterface::GDIMAGE_JPG;
+
+ /**
+ * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_GIF instead
+ * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_GIF
+ * @var string
+ */
+ public const OUTPUT_IMAGE_GIF = QROutputInterface::GDIMAGE_GIF;
+
+ /**
+ * @deprecated 5.0.0 use QROutputInterface::STRING_JSON instead
+ * @see \chillerlan\QRCode\Output\QROutputInterface::STRING_JSON
+ * @var string
+ */
+ public const OUTPUT_STRING_JSON = QROutputInterface::STRING_JSON;
+
+ /**
+ * @deprecated 5.0.0 use QROutputInterface::STRING_TEXT instead
+ * @see \chillerlan\QRCode\Output\QROutputInterface::STRING_TEXT
+ * @var string
+ */
+ public const OUTPUT_STRING_TEXT = QROutputInterface::STRING_TEXT;
+
+ /**
+ * @deprecated 5.0.0 use QROutputInterface::IMAGICK instead
+ * @see \chillerlan\QRCode\Output\QROutputInterface::IMAGICK
+ * @var string
+ */
+ public const OUTPUT_IMAGICK = QROutputInterface::IMAGICK;
+
+ /**
+ * @deprecated 5.0.0 use QROutputInterface::FPDF instead
+ * @see \chillerlan\QRCode\Output\QROutputInterface::FPDF
+ * @var string
+ */
+ public const OUTPUT_FPDF = QROutputInterface::FPDF;
+
+ /**
+ * @deprecated 5.0.0 use QROutputInterface::EPS instead
+ * @see \chillerlan\QRCode\Output\QROutputInterface::EPS
+ * @var string
+ */
+ public const OUTPUT_EPS = QROutputInterface::EPS;
+
+ /**
+ * @deprecated 5.0.0 use QROutputInterface::CUSTOM instead
+ * @see \chillerlan\QRCode\Output\QROutputInterface::CUSTOM
+ * @var string
+ */
+ public const OUTPUT_CUSTOM = QROutputInterface::CUSTOM;
+
+ /**
+ * @deprecated 5.0.0 use QROutputInterface::MODES instead
+ * @see \chillerlan\QRCode\Output\QROutputInterface::MODES
* @var string[]
*/
- protected const DATA_INTERFACES = [
- 'number' => Number::class,
- 'alphanum' => AlphaNum::class,
- 'kanji' => Kanji::class,
- 'byte' => Byte::class,
- ];
+ public const OUTPUT_MODES = QROutputInterface::MODES;
/**
* The settings container
@@ -158,26 +166,73 @@ class QRCode{
protected SettingsContainerInterface $options;
/**
- * The selected data interface (Number, AlphaNum, Kanji, Byte)
+ * A collection of one or more data segments of QRDataModeInterface instances to write
+ *
+ * @var \chillerlan\QRCode\Data\QRDataModeInterface[]
*/
- protected QRDataInterface $dataInterface;
+ protected array $dataSegments = [];
+
+ /**
+ * The luminance source for the reader
+ */
+ protected string $luminanceSourceFQN = GDLuminanceSource::class;
/**
* QRCode constructor.
*
- * Sets the options instance, determines the current mb-encoding and sets it to UTF-8
+ * PHP8: accept iterable
+ */
+ public function __construct(?SettingsContainerInterface $options = null){
+ $this->setOptions(($options ?? new QROptions));
+ }
+
+ /**
+ * Sets an options instance
+ */
+ public function setOptions(SettingsContainerInterface $options):self{
+ $this->options = $options;
+
+ if($this->options->readerUseImagickIfAvailable){
+ $this->luminanceSourceFQN = IMagickLuminanceSource::class;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Renders a QR Code for the given $data and QROptions, saves $file optionally
+ *
+ * Note: it is possible to add several data segments before calling this method with a valid $data string
+ * which will result in a mixed-mode QR Code with the given parameter as last element.
+ *
+ * @see https://github.com/chillerlan/php-qrcode/issues/246
+ *
+ * @return mixed
*/
- public function __construct(SettingsContainerInterface $options = null){
- $this->options = $options ?? new QROptions;
+ public function render(?string $data = null, ?string $file = null){
+
+ if($data !== null){
+ /** @var \chillerlan\QRCode\Data\QRDataModeInterface $dataInterface */
+ foreach(Mode::INTERFACES as $dataInterface){
+
+ if($dataInterface::validateString($data)){
+ $this->addSegment(new $dataInterface($data));
+
+ break;
+ }
+ }
+ }
+
+ return $this->renderMatrix($this->getQRMatrix(), $file);
}
/**
- * Renders a QR Code for the given $data and QROptions
+ * Renders a QR Code for the given QRMatrix and QROptions, saves $file optionally
*
* @return mixed
*/
- public function render(string $data, string $file = null){
- return $this->initOutputInterface($data)->dump($file);
+ public function renderMatrix(QRMatrix $matrix, ?string $file = null){
+ return $this->initOutputInterface($matrix)->dump($file ?? $this->options->cachefile);
}
/**
@@ -185,21 +240,37 @@ class QRCode{
*
* @throws \chillerlan\QRCode\Data\QRCodeDataException
*/
- public function getMatrix(string $data):QRMatrix{
+ public function getQRMatrix():QRMatrix{
+ $matrix = (new QRData($this->options, $this->dataSegments))->writeMatrix();
- if(empty($data)){
- throw new QRCodeDataException('QRCode::getMatrix() No data given.');
- }
+ $maskPattern = $this->options->maskPattern === MaskPattern::AUTO
+ ? MaskPattern::getBestPattern($matrix)
+ : new MaskPattern($this->options->maskPattern);
- $this->dataInterface = $this->initDataInterface($data);
+ $matrix->setFormatInfo($maskPattern)->mask($maskPattern);
- $maskPattern = $this->options->maskPattern === $this::MASK_PATTERN_AUTO
- ? (new MaskPatternTester($this->dataInterface))->getBestMaskPattern()
- : $this->options->maskPattern;
+ return $this->addMatrixModifications($matrix);
+ }
- $matrix = $this->dataInterface->initMatrix($maskPattern);
+ /**
+ * add matrix modifications after mask pattern evaluation and before handing over to output
+ */
+ protected function addMatrixModifications(QRMatrix $matrix):QRMatrix{
+
+ if($this->options->addLogoSpace){
+ // check whether one of the dimensions was omitted
+ $logoSpaceWidth = ($this->options->logoSpaceWidth ?? $this->options->logoSpaceHeight ?? 0);
+ $logoSpaceHeight = ($this->options->logoSpaceHeight ?? $logoSpaceWidth);
+
+ $matrix->setLogoSpace(
+ $logoSpaceWidth,
+ $logoSpaceHeight,
+ $this->options->logoSpaceStartX,
+ $this->options->logoSpaceStartY
+ );
+ }
- if((bool)$this->options->addQuietzone){
+ if($this->options->addQuietzone){
$matrix->setQuietZone($this->options->quietzoneSize);
}
@@ -207,107 +278,207 @@ class QRCode{
}
/**
- * returns a fresh QRDataInterface for the given $data
+ * @deprecated 5.0.0 use QRCode::getQRMatrix() instead
+ * @see \chillerlan\QRCode\QRCode::getQRMatrix()
+ * @codeCoverageIgnore
+ */
+ public function getMatrix():QRMatrix{
+ return $this->getQRMatrix();
+ }
+
+ /**
+ * initializes a fresh built-in or custom QROutputInterface
*
- * @throws \chillerlan\QRCode\Data\QRCodeDataException
+ * @throws \chillerlan\QRCode\Output\QRCodeOutputException
*/
- public function initDataInterface(string $data):QRDataInterface{
+ protected function initOutputInterface(QRMatrix $matrix):QROutputInterface{
+ // @todo: remove custom invocation in v6
+ $outputInterface = (QROutputInterface::MODES[$this->options->outputType] ?? null);
- // allow forcing the data mode
- // see https://github.com/chillerlan/php-qrcode/issues/39
- $interface = $this::DATA_INTERFACES[strtolower($this->options->dataModeOverride)] ?? null;
+ if($this->options->outputType === QROutputInterface::CUSTOM){
+ $outputInterface = $this->options->outputInterface;
+ }
- if($interface !== null){
- return new $interface($this->options, $data);
+ if(!$outputInterface || !class_exists($outputInterface)){
+ throw new QRCodeOutputException('invalid output module');
}
- foreach($this::DATA_INTERFACES as $mode => $dataInterface){
+ if(!in_array(QROutputInterface::class, class_implements($outputInterface), true)){
+ throw new QRCodeOutputException('output module does not implement QROutputInterface');
+ }
- if(call_user_func_array([$this, 'is'.$mode], [$data])){
- return new $dataInterface($this->options, $data);
- }
+ return new $outputInterface($this->options, $matrix);
+ }
- }
+ /**
+ * checks if a string qualifies as numeric (convenience method)
+ *
+ * @deprecated 5.0.0 use Number::validateString() instead
+ * @see \chillerlan\QRCode\Data\Number::validateString()
+ * @codeCoverageIgnore
+ */
+ public function isNumber(string $string):bool{
+ return Number::validateString($string);
+ }
- throw new QRCodeDataException('invalid data type'); // @codeCoverageIgnore
+ /**
+ * checks if a string qualifies as alphanumeric (convenience method)
+ *
+ * @deprecated 5.0.0 use AlphaNum::validateString() instead
+ * @see \chillerlan\QRCode\Data\AlphaNum::validateString()
+ * @codeCoverageIgnore
+ */
+ public function isAlphaNum(string $string):bool{
+ return AlphaNum::validateString($string);
}
/**
- * returns a fresh (built-in) QROutputInterface
+ * checks if a string qualifies as Kanji (convenience method)
*
- * @throws \chillerlan\QRCode\Output\QRCodeOutputException
+ * @deprecated 5.0.0 use Kanji::validateString() instead
+ * @see \chillerlan\QRCode\Data\Kanji::validateString()
+ * @codeCoverageIgnore
*/
- protected function initOutputInterface(string $data):QROutputInterface{
+ public function isKanji(string $string):bool{
+ return Kanji::validateString($string);
+ }
- if($this->options->outputType === $this::OUTPUT_CUSTOM && class_exists($this->options->outputInterface)){
- /** @phan-suppress-next-line PhanTypeExpectedObjectOrClassName */
- return new $this->options->outputInterface($this->options, $this->getMatrix($data));
- }
+ /**
+ * a dummy (convenience method)
+ *
+ * @deprecated 5.0.0 use Byte::validateString() instead
+ * @see \chillerlan\QRCode\Data\Byte::validateString()
+ * @codeCoverageIgnore
+ */
+ public function isByte(string $string):bool{
+ return Byte::validateString($string);
+ }
- foreach($this::OUTPUT_MODES as $outputInterface => $modes){
+ /**
+ * Adds a data segment
+ *
+ * ISO/IEC 18004:2000 8.3.6 - Mixing modes
+ * ISO/IEC 18004:2000 Annex H - Optimisation of bit stream length
+ */
+ public function addSegment(QRDataModeInterface $segment):self{
+ $this->dataSegments[] = $segment;
- if(in_array($this->options->outputType, $modes, true) && class_exists($outputInterface)){
- return new $outputInterface($this->options, $this->getMatrix($data));
- }
+ return $this;
+ }
- }
+ /**
+ * Clears the data segments array
+ *
+ * @codeCoverageIgnore
+ */
+ public function clearSegments():self{
+ $this->dataSegments = [];
- throw new QRCodeOutputException('invalid output type');
+ return $this;
}
/**
- * checks if a string qualifies as numeric
+ * Adds a numeric data segment
+ *
+ * ISO/IEC 18004:2000 8.3.2 - Numeric Mode
*/
- public function isNumber(string $string):bool{
- return $this->checkString($string, QRDataInterface::CHAR_MAP_NUMBER);
+ public function addNumericSegment(string $data):self{
+ return $this->addSegment(new Number($data));
}
/**
- * checks if a string qualifies as alphanumeric
+ * Adds an alphanumeric data segment
+ *
+ * ISO/IEC 18004:2000 8.3.3 - Alphanumeric Mode
*/
- public function isAlphaNum(string $string):bool{
- return $this->checkString($string, QRDataInterface::CHAR_MAP_ALPHANUM);
+ public function addAlphaNumSegment(string $data):self{
+ return $this->addSegment(new AlphaNum($data));
}
/**
- * checks is a given $string matches the characters of a given $charmap, returns false on the first invalid occurence.
+ * Adds a Kanji data segment (Japanese 13-bit double-byte characters, Shift-JIS)
+ *
+ * ISO/IEC 18004:2000 8.3.5 - Kanji Mode
*/
- protected function checkString(string $string, array $charmap):bool{
+ public function addKanjiSegment(string $data):self{
+ return $this->addSegment(new Kanji($data));
+ }
- foreach(str_split($string) as $chr){
- if(!isset($charmap[$chr])){
- return false;
- }
- }
+ /**
+ * Adds a Hanzi data segment (simplified Chinese 13-bit double-byte characters, GB2312/GB18030)
+ *
+ * GBT18284-2000 Hanzi Mode
+ */
+ public function addHanziSegment(string $data):self{
+ return $this->addSegment(new Hanzi($data));
+ }
- return true;
+ /**
+ * Adds an 8-bit byte data segment
+ *
+ * ISO/IEC 18004:2000 8.3.4 - 8-bit Byte Mode
+ */
+ public function addByteSegment(string $data):self{
+ return $this->addSegment(new Byte($data));
}
/**
- * checks if a string qualifies as Kanji
+ * Adds a standalone ECI designator
+ *
+ * The ECI designator must be followed by a Byte segment that contains the string encoded according to the given ECI charset
+ *
+ * ISO/IEC 18004:2000 8.3.1 - Extended Channel Interpretation (ECI) Mode
*/
- public function isKanji(string $string):bool{
- $i = 0;
- $len = strlen($string);
+ public function addEciDesignator(int $encoding):self{
+ return $this->addSegment(new ECI($encoding));
+ }
- while($i + 1 < $len){
- $c = ((0xff & ord($string[$i])) << 8) | (0xff & ord($string[$i + 1]));
+ /**
+ * Adds an ECI data segment (including designator)
+ *
+ * The given string will be encoded from mb_internal_encoding() to the given ECI character set
+ *
+ * I hate this somehow, but I'll leave it for now
+ *
+ * @throws \chillerlan\QRCode\QRCodeException
+ */
+ public function addEciSegment(int $encoding, string $data):self{
+ // validate the encoding id
+ $eciCharset = new ECICharset($encoding);
+ // get charset name
+ $eciCharsetName = $eciCharset->getName();
+ // convert the string to the given charset
+ if($eciCharsetName !== null){
+ $data = mb_convert_encoding($data, $eciCharsetName, mb_internal_encoding());
+
+ return $this
+ ->addEciDesignator($eciCharset->getID())
+ ->addByteSegment($data)
+ ;
+ }
- if(!($c >= 0x8140 && $c <= 0x9FFC) && !($c >= 0xE040 && $c <= 0xEBBF)){
- return false;
- }
+ throw new QRCodeException('unable to add ECI segment');
+ }
- $i += 2;
- }
+ /**
+ * Reads a QR Code from a given file
+ */
+ public function readFromFile(string $path):DecoderResult{
+ return $this->readFromSource($this->luminanceSourceFQN::fromFile($path, $this->options));
+ }
- return $i >= $len;
+ /**
+ * Reads a QR Code from the given data blob
+ */
+ public function readFromBlob(string $blob):DecoderResult{
+ return $this->readFromSource($this->luminanceSourceFQN::fromBlob($blob, $this->options));
}
/**
- * a dummy
+ * Reads a QR Code from the given luminance source
*/
- public function isByte(string $data):bool{
- return $data !== '';
+ public function readFromSource(LuminanceSourceInterface $source):DecoderResult{
+ return (new Decoder)->decode($source);
}
}
diff --git a/vendor/chillerlan/php-qrcode/src/QRCodeException.php b/vendor/chillerlan/php-qrcode/src/QRCodeException.php
index 737a0803e..600ce50ea 100644
--- a/vendor/chillerlan/php-qrcode/src/QRCodeException.php
+++ b/vendor/chillerlan/php-qrcode/src/QRCodeException.php
@@ -2,9 +2,7 @@
/**
* Class QRCodeException
*
- * @filesource QRCodeException.php
* @created 27.11.2015
- * @package chillerlan\QRCode
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
@@ -17,4 +15,6 @@ use Exception;
/**
* An exception container
*/
-class QRCodeException extends Exception{}
+class QRCodeException extends Exception{
+
+}
diff --git a/vendor/chillerlan/php-qrcode/src/QROptions.php b/vendor/chillerlan/php-qrcode/src/QROptions.php
index e36f6701a..91b5b4568 100644
--- a/vendor/chillerlan/php-qrcode/src/QROptions.php
+++ b/vendor/chillerlan/php-qrcode/src/QROptions.php
@@ -2,9 +2,7 @@
/**
* Class QROptions
*
- * @filesource QROptions.php
* @created 08.12.2015
- * @package chillerlan\QRCode
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
@@ -16,38 +14,6 @@ use chillerlan\Settings\SettingsContainerAbstract;
/**
* The QRCode settings container
- *
- * @property int $version
- * @property int $versionMin
- * @property int $versionMax
- * @property int $eccLevel
- * @property int $maskPattern
- * @property bool $addQuietzone
- * @property int $quietzoneSize
- * @property string|null $dataModeOverride
- * @property string $outputType
- * @property string|null $outputInterface
- * @property string|null $cachefile
- * @property string $eol
- * @property int $scale
- * @property string $cssClass
- * @property float $svgOpacity
- * @property string $svgDefs
- * @property int $svgViewBoxSize
- * @property string $textDark
- * @property string $textLight
- * @property string $markupDark
- * @property string $markupLight
- * @property bool $returnResource
- * @property bool $imageBase64
- * @property bool $imageTransparent
- * @property array $imageTransparencyBG
- * @property int $pngCompression
- * @property int $jpegQuality
- * @property string $imagickFormat
- * @property string|null $imagickBG
- * @property string $fpdfMeasureUnit
- * @property array|null $moduleValues
*/
class QROptions extends SettingsContainerAbstract{
use QROptionsTrait;
diff --git a/vendor/chillerlan/php-qrcode/src/QROptionsTrait.php b/vendor/chillerlan/php-qrcode/src/QROptionsTrait.php
index 74c384b13..d2bc8c2ce 100644
--- a/vendor/chillerlan/php-qrcode/src/QROptionsTrait.php
+++ b/vendor/chillerlan/php-qrcode/src/QROptionsTrait.php
@@ -2,232 +2,493 @@
/**
* Trait QROptionsTrait
*
- * @filesource QROptionsTrait.php
+ * Note: the docblocks in this file are optimized for readability in PhpStorm ond on readthedocs.io
+ *
* @created 10.03.2018
- * @package chillerlan\QRCode
* @author smiley <smiley@chillerlan.net>
* @copyright 2018 smiley
* @license MIT
*
- * @noinspection PhpUnused
+ * @noinspection PhpUnused, PhpComposerExtensionStubsInspection
*/
namespace chillerlan\QRCode;
-use function array_values, count, in_array, is_numeric, max, min, sprintf, strtolower;
+use chillerlan\QRCode\Output\QROutputInterface;
+use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Version};
+use function extension_loaded, in_array, max, min, strtolower;
+use const JSON_THROW_ON_ERROR, PHP_EOL;
/**
* The QRCode plug-in settings & setter functionality
*/
trait QROptionsTrait{
+ /*
+ * QR Code specific settings
+ */
+
/**
* QR Code version number
*
- * [1 ... 40] or QRCode::VERSION_AUTO
+ * `1 ... 40` or `Version::AUTO` (default)
+ *
+ * @see \chillerlan\QRCode\Common\Version
*/
- protected int $version = QRCode::VERSION_AUTO;
+ protected int $version = Version::AUTO;
/**
* Minimum QR version
*
- * if $version = QRCode::VERSION_AUTO
+ * if `QROptions::$version` is set to `Version::AUTO` (default: 1)
*/
protected int $versionMin = 1;
/**
* Maximum QR version
+ *
+ * if `QROptions::$version` is set to `Version::AUTO` (default: 40)
*/
protected int $versionMax = 40;
/**
* Error correct level
*
- * QRCode::ECC_X where X is:
+ * `EccLevel::X` where `X` is:
+ *
+ * - `L` => 7% (default)
+ * - `M` => 15%
+ * - `Q` => 25%
+ * - `H` => 30%
*
- * - L => 7%
- * - M => 15%
- * - Q => 25%
- * - H => 30%
+ * @todo: accept string values (PHP8+)
+ * @see \chillerlan\QRCode\Common\EccLevel
+ * @see https://github.com/chillerlan/php-qrcode/discussions/160
*/
- protected int $eccLevel = QRCode::ECC_L;
+ protected int $eccLevel = EccLevel::L;
/**
- * Mask Pattern to use
+ * Mask Pattern to use (no value in using, mostly for unit testing purposes)
*
- * [0...7] or QRCode::MASK_PATTERN_AUTO
+ * `0 ... 7` or `MaskPattern::PATTERN_AUTO` (default)
+ *
+ * @see \chillerlan\QRCode\Common\MaskPattern
*/
- protected int $maskPattern = QRCode::MASK_PATTERN_AUTO;
+ protected int $maskPattern = MaskPattern::AUTO;
/**
* Add a "quiet zone" (margin) according to the QR code spec
+ *
+ * @see https://www.qrcode.com/en/howto/code.html
*/
protected bool $addQuietzone = true;
/**
* Size of the quiet zone
*
- * internally clamped to [0 ... $moduleCount / 2], defaults to 4 modules
+ * internally clamped to `0 ... $moduleCount / 2` (default: 4)
*/
protected int $quietzoneSize = 4;
+
+ /*
+ * General output settings
+ */
+
/**
- * Use this to circumvent the data mode detection and force the usage of the given mode.
+ * The built-in output type
+ *
+ * - `QROutputInterface::MARKUP_SVG` (default)
+ * - `QROutputInterface::MARKUP_HTML`
+ * - `QROutputInterface::GDIMAGE_BMP`
+ * - `QROutputInterface::GDIMAGE_GIF`
+ * - `QROutputInterface::GDIMAGE_JPG`
+ * - `QROutputInterface::GDIMAGE_PNG`
+ * - `QROutputInterface::GDIMAGE_WEBP`
+ * - `QROutputInterface::STRING_TEXT`
+ * - `QROutputInterface::STRING_JSON`
+ * - `QROutputInterface::IMAGICK`
+ * - `QROutputInterface::EPS`
+ * - `QROutputInterface::FPDF`
+ * - `QROutputInterface::CUSTOM`
*
- * valid modes are: Number, AlphaNum, Kanji, Byte (case insensitive)
+ * @see \chillerlan\QRCode\Output\QREps
+ * @see \chillerlan\QRCode\Output\QRFpdf
+ * @see \chillerlan\QRCode\Output\QRGdImage
+ * @see \chillerlan\QRCode\Output\QRImagick
+ * @see \chillerlan\QRCode\Output\QRMarkupHTML
+ * @see \chillerlan\QRCode\Output\QRMarkupSVG
+ * @see \chillerlan\QRCode\Output\QRString
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
*
- * @see https://github.com/chillerlan/php-qrcode/issues/39
- * @see https://github.com/chillerlan/php-qrcode/issues/97 (changed default value to '')
+ * @deprecated 5.0.0 see issue #223
*/
- protected string $dataModeOverride = '';
+ protected string $outputType = QROutputInterface::MARKUP_SVG;
/**
- * The output type
+ * The FQCN of the custom `QROutputInterface`
*
- * - QRCode::OUTPUT_MARKUP_XXXX where XXXX = HTML, SVG
- * - QRCode::OUTPUT_IMAGE_XXX where XXX = PNG, GIF, JPG
- * - QRCode::OUTPUT_STRING_XXXX where XXXX = TEXT, JSON
- * - QRCode::OUTPUT_CUSTOM
+ * if `QROptions::$outputType` is set to `QROutputInterface::CUSTOM` (default: `null`)
+ *
+ * @deprecated 5.0.0 the nullable type will be removed in future versions
+ * and the default value will be set to `QRMarkupSVG::class`
*/
- protected string $outputType = QRCode::OUTPUT_IMAGE_PNG;
+ protected ?string $outputInterface = null;
/**
- * the FQCN of the custom QROutputInterface if $outputType is set to QRCode::OUTPUT_CUSTOM
+ * Return the image resource instead of a render if applicable.
+ *
+ * - `QRGdImage`: `resource` (PHP < 8), `GdImage`
+ * - `QRImagick`: `Imagick`
+ * - `QRFpdf`: `FPDF`
+ *
+ * This option overrides/ignores other output settings, such as `QROptions::$cachefile`
+ * and `QROptions::$outputBase64`. (default: `false`)
+ *
+ * @see \chillerlan\QRCode\Output\QROutputInterface::dump()
*/
- protected ?string $outputInterface = null;
+ protected bool $returnResource = false;
/**
- * /path/to/cache.file
+ * Optional cache file path `/path/to/cache.file`
+ *
+ * Please note that the `$file` parameter in `QRCode::render()` and `QRCode::renderMatrix()`
+ * takes precedence over the `QROptions::$cachefile` value. (default: `null`)
+ *
+ * @see \chillerlan\QRCode\QRCode::render()
+ * @see \chillerlan\QRCode\QRCode::renderMatrix()
*/
protected ?string $cachefile = null;
/**
- * newline string [HTML, SVG, TEXT]
+ * Toggle base64 data URI or raw data output (if applicable)
+ *
+ * (default: `true`)
+ *
+ * @see \chillerlan\QRCode\Output\QROutputAbstract::toBase64DataURI()
*/
- protected string $eol = PHP_EOL;
+ protected bool $outputBase64 = true;
/**
- * size of a QR code pixel [SVG, IMAGE_*], HTML via CSS
+ * Newline string
+ *
+ * (default: `PHP_EOL`)
+ */
+ protected string $eol = PHP_EOL;
+
+ /*
+ * Common visual modifications
*/
- protected int $scale = 5;
/**
- * a common css class
+ * Sets the image background color (if applicable)
+ *
+ * - `QRImagick`: defaults to `"white"`
+ * - `QRGdImage`: defaults to `[255, 255, 255]`
+ * - `QRFpdf`: defaults to blank internally (white page)
+ *
+ * @var mixed|null
*/
- protected string $cssClass = '';
+ protected $bgColor = null;
/**
- * SVG opacity
+ * Whether to invert the matrix (reflectance reversal)
+ *
+ * (default: `false`)
+ *
+ * @see \chillerlan\QRCode\Data\QRMatrix::invert()
*/
- protected float $svgOpacity = 1.0;
+ protected bool $invertMatrix = false;
/**
- * anything between <defs>
+ * Whether to draw the light (false) modules
*
- * @see https://developer.mozilla.org/docs/Web/SVG/Element/defs
+ * (default: `true`)
*/
- protected string $svgDefs = '<style>rect{shape-rendering:crispEdges}</style>';
+ protected bool $drawLightModules = true;
/**
- * SVG viewBox size. a single integer number which defines width/height of the viewBox attribute.
+ * Specify whether to draw the modules as filled circles
+ *
+ * a note for `GdImage` output:
+ *
+ * if `QROptions::$scale` is less than 20, the image will be upscaled internally, then the modules will be drawn
+ * using `imagefilledellipse()` and then scaled back to the expected size
*
- * viewBox="0 0 x x"
+ * No effect in: `QREps`, `QRFpdf`, `QRMarkupHTML`
*
- * @see https://css-tricks.com/scale-svg/#article-header-id-3
+ * @see \imagefilledellipse()
+ * @see https://github.com/chillerlan/php-qrcode/issues/23
+ * @see https://github.com/chillerlan/php-qrcode/discussions/122
*/
- protected ?int $svgViewBoxSize = null;
+ protected bool $drawCircularModules = false;
/**
- * string substitute for dark
+ * Specifies the radius of the modules when `QROptions::$drawCircularModules` is set to `true`
+ *
+ * (default: 0.45)
*/
- protected string $textDark = '🔴';
+ protected float $circleRadius = 0.45;
/**
- * string substitute for light
+ * Specifies which module types to exclude when `QROptions::$drawCircularModules` is set to `true`
+ *
+ * (default: `[]`)
*/
- protected string $textLight = 'â­•';
+ protected array $keepAsSquare = [];
/**
- * markup substitute for dark (CSS value)
+ * Whether to connect the paths for the several module types to avoid weird glitches when using gradients etc.
+ *
+ * This option is exclusive to output classes that use the module collector `QROutputAbstract::collectModules()`,
+ * which converts the `$M_TYPE` of all modules to `QRMatrix::M_DATA` and `QRMatrix::M_DATA_DARK` respectively.
+ *
+ * Module types that should not be added to the connected path can be excluded via `QROptions::$excludeFromConnect`.
+ *
+ * Currentty used in `QREps` and `QRMarkupSVG`.
+ *
+ * @see \chillerlan\QRCode\Output\QROutputAbstract::collectModules()
+ * @see \chillerlan\QRCode\QROptionsTrait::$excludeFromConnect
+ * @see https://github.com/chillerlan/php-qrcode/issues/57
*/
- protected string $markupDark = '#000';
+ protected bool $connectPaths = false;
/**
- * markup substitute for light (CSS value)
+ * Specify which paths/patterns to exclude from connecting if `QROptions::$connectPaths` is set to `true`
+ *
+ * @see \chillerlan\QRCode\QROptionsTrait::$connectPaths
*/
- protected string $markupLight = '#fff';
+ protected array $excludeFromConnect = [];
/**
- * Return the image resource instead of a render if applicable.
- * This option overrides other output options, such as $cachefile and $imageBase64.
+ * Module values map
*
- * Supported by the following modules:
+ * - `QRImagick`, `QRMarkupHTML`, `QRMarkupSVG`: #ABCDEF, cssname, rgb(), rgba()...
+ * - `QREps`, `QRFpdf`, `QRGdImage`: `[R, G, B]` // 0-255
+ * - `QREps`: `[C, M, Y, K]` // 0-255
*
- * - QRImage: resource (PHP < 8), GdImage
- * - QRImagick: Imagick
- * - QRFpdf: FPDF
+ * @see \chillerlan\QRCode\Output\QROutputAbstract::setModuleValues()
+ */
+ protected array $moduleValues = [];
+
+ /**
+ * Toggles logo space creation
*
- * @see \chillerlan\QRCode\Output\QROutputInterface::dump()
+ * @see \chillerlan\QRCode\QRCode::addMatrixModifications()
+ * @see \chillerlan\QRCode\Data\QRMatrix::setLogoSpace()
+ */
+ protected bool $addLogoSpace = false;
+
+ /**
+ * Width of the logo space
*
- * @var bool
+ * if only `QROptions::$logoSpaceWidth` is given, the logo space is assumed a square of that size
*/
- protected bool $returnResource = false;
+ protected ?int $logoSpaceWidth = null;
+
+ /**
+ * Height of the logo space
+ *
+ * if only `QROptions::$logoSpaceHeight` is given, the logo space is assumed a square of that size
+ */
+ protected ?int $logoSpaceHeight = null;
/**
- * toggle base64 or raw image data
+ * Optional horizontal start position of the logo space (top left corner)
*/
- protected bool $imageBase64 = true;
+ protected ?int $logoSpaceStartX = null;
/**
- * toggle transparency, not supported by jpg
+ * Optional vertical start position of the logo space (top left corner)
*/
- protected bool $imageTransparent = true;
+ protected ?int $logoSpaceStartY = null;
+
+
+ /*
+ * Common raster image settings (QRGdImage, QRImagick)
+ */
+
+ /**
+ * Pixel size of a QR code module
+ */
+ protected int $scale = 5;
/**
- * @see imagecolortransparent()
+ * Toggle transparency
+ *
+ * - `QRGdImage` and `QRImagick`: the given `QROptions::$transparencyColor` is set as transparent
*
- * [R, G, B]
+ * @see https://github.com/chillerlan/php-qrcode/discussions/121
*/
- protected array $imageTransparencyBG = [255, 255, 255];
+ protected bool $imageTransparent = false;
/**
- * @see imagepng()
+ * Sets a transparency color for when `QROptions::$imageTransparent` is set to `true`.
+ *
+ * Defaults to `QROptions::$bgColor`.
+ *
+ * - `QRGdImage`: `[R, G, B]`, this color is set as transparent in `imagecolortransparent()`
+ * - `QRImagick`: `"color_str"`, this color is set in `Imagick::transparentPaintImage()`
+ *
+ * @see \imagecolortransparent()
+ * @see \Imagick::transparentPaintImage()
+ *
+ * @var mixed|null
*/
- protected int $pngCompression = -1;
+ protected $transparencyColor = null;
/**
- * @see imagejpeg()
+ * Compression quality
+ *
+ * The given value depends on the used output type:
+ *
+ * - `QRGdImageBMP`: `[0...1]`
+ * - `QRGdImageJPEG`: `[0...100]`
+ * - `QRGdImageWEBP`: `[0...9]`
+ * - `QRGdImagePNG`: `[0...100]`
+ * - `QRImagick`: `[0...100]`
+ *
+ * @see \imagebmp()
+ * @see \imagejpeg()
+ * @see \imagepng()
+ * @see \imagewebp()
+ * @see \Imagick::setImageCompressionQuality()
+ */
+ protected int $quality = -1;
+
+ /*
+ * QRGdImage settings
+ */
+
+ /**
+ * Toggles the usage of internal upscaling when `QROptions::$drawCircularModules` is set to `true` and
+ * `QROptions::$scale` is less than 20
+ *
+ * @see \chillerlan\QRCode\Output\QRGdImage::createImage()
+ * @see https://github.com/chillerlan/php-qrcode/issues/23
+ */
+ protected bool $gdImageUseUpscale = true;
+
+ /*
+ * QRImagick settings
*/
- protected int $jpegQuality = 85;
/**
* Imagick output format
*
- * @see \Imagick::setType()
+ * @see \Imagick::setImageFormat()
+ * @see https://www.imagemagick.org/script/formats.php
+ */
+ protected string $imagickFormat = 'png32';
+
+
+ /*
+ * Common markup output settings (QRMarkupSVG, QRMarkupHTML)
+ */
+
+ /**
+ * A common css class
+ */
+ protected string $cssClass = 'qrcode';
+
+ /*
+ * QRMarkupSVG settings
+ */
+
+ /**
+ * Whether to add an XML header line or not, e.g. to embed the SVG directly in HTML
+ *
+ * `<?xml version="1.0" encoding="UTF-8"?>`
*/
- protected string $imagickFormat = 'png';
+ protected bool $svgAddXmlHeader = true;
/**
- * Imagick background color (defaults to "transparent")
+ * Anything in the SVG `<defs>` tag
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs
+ */
+ protected string $svgDefs = '';
+
+ /**
+ * Sets the value for the "preserveAspectRatio" on the `<svg>` element
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio
+ */
+ protected string $svgPreserveAspectRatio = 'xMidYMid';
+
+ /**
+ * Whether to use the SVG `fill` attributes
+ *
+ * If set to `true` (default), the `fill` attribute will be set with the module value for the `<path>` element's `$M_TYPE`.
+ * When set to `false`, the module values map will be ignored and the QR Code may be styled via CSS.
*
- * @see \ImagickPixel::__construct()
+ * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill
+ */
+ protected bool $svgUseFillAttributes = true;
+
+ /*
+ * QRStringText settings
+ */
+
+ /**
+ * An optional line prefix, e.g. empty space to align the QR Code in a console
+ */
+ protected string $textLineStart = '';
+
+ /*
+ * QRStringJSON settings
+ */
+
+ /**
+ * Sets the flags to use for the `json_encode()` call
+ *
+ * @see https://www.php.net/manual/json.constants.php
+ */
+ protected int $jsonFlags = JSON_THROW_ON_ERROR;
+
+ /**
+ * Whether to return matrix values in JSON as booleans or `$M_TYPE` integers
+ */
+ protected bool $jsonAsBooleans = false;
+
+ /*
+ * QRFpdf settings
*/
- protected ?string $imagickBG = null;
/**
- * Measurement unit for FPDF output: pt, mm, cm, in (defaults to "pt")
+ * Measurement unit for `FPDF` output: `pt`, `mm`, `cm`, `in` (default: `pt`)
*
- * @see \FPDF::__construct()
+ * @see FPDF::__construct()
*/
protected string $fpdfMeasureUnit = 'pt';
+
+ /*
+ * QR Code reader settings
+ */
+
/**
- * Module values map
+ * Use Imagick (if available) when reading QR Codes
+ */
+ protected bool $readerUseImagickIfAvailable = false;
+
+ /**
+ * Grayscale the image before reading
+ */
+ protected bool $readerGrayscale = false;
+
+ /**
+ * Invert the colors of the image
+ */
+ protected bool $readerInvertColors = false;
+
+ /**
+ * Increase the contrast before reading
*
- * - HTML, IMAGICK: #ABCDEF, cssname, rgb(), rgba()...
- * - IMAGE: [63, 127, 255] // R, G, B
+ * note that applying contrast works different in GD and Imagick, so mileage may vary
*/
- protected ?array $moduleValues = null;
+ protected bool $readerIncreaseContrast = false;
+
/**
* clamp min/max version number
@@ -255,87 +516,214 @@ trait QROptionsTrait{
}
/**
- * sets the error correction level
+ * sets/clamps the version number
+ */
+ protected function set_version(int $version):void{
+ $this->version = ($version !== Version::AUTO) ? max(1, min(40, $version)) : Version::AUTO;
+ }
+
+ /**
+ * sets/clamps the quiet zone size
+ */
+ protected function set_quietzoneSize(int $quietzoneSize):void{
+ $this->quietzoneSize = max(0, min($quietzoneSize, 75));
+ }
+
+ /**
+ * sets the FPDF measurement unit
*
- * @throws \chillerlan\QRCode\QRCodeException
+ * @codeCoverageIgnore
*/
- protected function set_eccLevel(int $eccLevel):void{
+ protected function set_fpdfMeasureUnit(string $unit):void{
+ $unit = strtolower($unit);
- if(!isset(QRCode::ECC_MODES[$eccLevel])){
- throw new QRCodeException(sprintf('Invalid error correct level: %s', $eccLevel));
+ if(in_array($unit, ['cm', 'in', 'mm', 'pt'], true)){
+ $this->fpdfMeasureUnit = $unit;
}
- $this->eccLevel = $eccLevel;
+ // @todo throw or ignore silently?
+ }
+
+ /**
+ * enables Imagick for the QR Code reader if the extension is available
+ */
+ protected function set_readerUseImagickIfAvailable(bool $useImagickIfAvailable):void{
+ $this->readerUseImagickIfAvailable = ($useImagickIfAvailable && extension_loaded('imagick'));
}
/**
- * sets/clamps the mask pattern
+ * clamp the logo space values between 0 and maximum length (177 modules at version 40)
*/
- protected function set_maskPattern(int $maskPattern):void{
+ protected function clampLogoSpaceValue(?int $value):?int{
- if($maskPattern !== QRCode::MASK_PATTERN_AUTO){
- $this->maskPattern = max(0, min(7, $maskPattern));
+ if($value === null){
+ return null;
}
+ return (int)max(0, min(177, $value));
}
/**
- * sets the transparency background color
- *
- * @throws \chillerlan\QRCode\QRCodeException
+ * clamp/set logo space width
*/
- protected function set_imageTransparencyBG(array $imageTransparencyBG):void{
+ protected function set_logoSpaceWidth(?int $value):void{
+ $this->logoSpaceWidth = $this->clampLogoSpaceValue($value);
+ }
- // invalid value - set to white as default
- if(count($imageTransparencyBG) < 3){
- $this->imageTransparencyBG = [255, 255, 255];
+ /**
+ * clamp/set logo space height
+ */
+ protected function set_logoSpaceHeight(?int $value):void{
+ $this->logoSpaceHeight = $this->clampLogoSpaceValue($value);
+ }
- return;
- }
+ /**
+ * clamp/set horizontal logo space start
+ */
+ protected function set_logoSpaceStartX(?int $value):void{
+ $this->logoSpaceStartX = $this->clampLogoSpaceValue($value);
+ }
- foreach($imageTransparencyBG as $k => $v){
+ /**
+ * clamp/set vertical logo space start
+ */
+ protected function set_logoSpaceStartY(?int $value):void{
+ $this->logoSpaceStartY = $this->clampLogoSpaceValue($value);
+ }
+
+ /**
+ * clamp/set SVG circle radius
+ */
+ protected function set_circleRadius(float $circleRadius):void{
+ $this->circleRadius = max(0.1, min(0.75, $circleRadius));
+ }
- // cut off exceeding items
- if($k > 2){
- break;
- }
+ /*
+ * redirect calls of deprecated variables to new/renamed property
+ */
- if(!is_numeric($v)){
- throw new QRCodeException('Invalid RGB value.');
- }
+ /**
+ * @deprecated 5.0.0 use QROptions::$outputBase64 instead
+ * @see \chillerlan\QRCode\QROptions::$outputBase64
+ */
+ protected bool $imageBase64;
- // clamp the values
- $this->imageTransparencyBG[$k] = max(0, min(255, (int)$v));
- }
+ /**
+ * redirect call to the new variable
+ *
+ * @deprecated 5.0.0 use QROptions::$outputBase64 instead
+ * @see \chillerlan\QRCode\QROptions::$outputBase64
+ * @codeCoverageIgnore
+ */
+ protected function set_imageBase64(bool $imageBase64):void{
+ $this->outputBase64 = $imageBase64;
+ }
- // use the array values to not run into errors with the spread operator (...$arr)
- $this->imageTransparencyBG = array_values($this->imageTransparencyBG);
+ /**
+ * redirect call to the new variable
+ *
+ * @deprecated 5.0.0 use QROptions::$outputBase64 instead
+ * @see \chillerlan\QRCode\QROptions::$outputBase64
+ * @codeCoverageIgnore
+ */
+ protected function get_imageBase64():bool{
+ return $this->outputBase64;
}
/**
- * sets/clamps the version number
+ * @deprecated 5.0.0 use QROptions::$quality instead
+ * @see \chillerlan\QRCode\QROptions::$quality
*/
- protected function set_version(int $version):void{
+ protected int $jpegQuality;
- if($version !== QRCode::VERSION_AUTO){
- $this->version = max(1, min(40, $version));
- }
+ /**
+ * @deprecated 5.0.0 use QROptions::$quality instead
+ * @see \chillerlan\QRCode\QROptions::$quality
+ * @codeCoverageIgnore
+ */
+ protected function set_jpegQuality(int $jpegQuality):void{
+ $this->quality = $jpegQuality;
+ }
+ /**
+ * @deprecated 5.0.0 use QROptions::$quality instead
+ * @see \chillerlan\QRCode\QROptions::$quality
+ * @codeCoverageIgnore
+ */
+ protected function get_jpegQuality():int{
+ return $this->quality;
}
/**
- * sets the FPDF measurement unit
- *
+ * @deprecated 5.0.0 use QROptions::$quality instead
+ * @see \chillerlan\QRCode\QROptions::$quality
+ */
+ protected int $pngCompression;
+
+ /**
+ * @deprecated 5.0.0 use QROptions::$quality instead
+ * @see \chillerlan\QRCode\QROptions::$quality
* @codeCoverageIgnore
*/
- protected function set_fpdfMeasureUnit(string $unit):void{
- $unit = strtolower($unit);
+ protected function set_pngCompression(int $pngCompression):void{
+ $this->quality = $pngCompression;
+ }
- if(in_array($unit, ['cm', 'in', 'mm', 'pt'], true)){
- $this->fpdfMeasureUnit = $unit;
- }
+ /**
+ * @deprecated 5.0.0 use QROptions::$quality instead
+ * @see \chillerlan\QRCode\QROptions::$quality
+ * @codeCoverageIgnore
+ */
+ protected function get_pngCompression():int{
+ return $this->quality;
+ }
- // @todo throw or ignore silently?
+ /**
+ * @deprecated 5.0.0 use QROptions::$transparencyColor instead
+ * @see \chillerlan\QRCode\QROptions::$transparencyColor
+ */
+ protected array $imageTransparencyBG;
+
+ /**
+ * @deprecated 5.0.0 use QROptions::$transparencyColor instead
+ * @see \chillerlan\QRCode\QROptions::$transparencyColor
+ * @codeCoverageIgnore
+ */
+ protected function set_imageTransparencyBG(?array $imageTransparencyBG):void{
+ $this->transparencyColor = $imageTransparencyBG;
+ }
+
+ /**
+ * @deprecated 5.0.0 use QROptions::$transparencyColor instead
+ * @see \chillerlan\QRCode\QROptions::$transparencyColor
+ * @codeCoverageIgnore
+ */
+ protected function get_imageTransparencyBG():?array{
+ return $this->transparencyColor;
+ }
+
+ /**
+ * @deprecated 5.0.0 use QROptions::$bgColor instead
+ * @see \chillerlan\QRCode\QROptions::$bgColor
+ */
+ protected string $imagickBG;
+
+ /**
+ * @deprecated 5.0.0 use QROptions::$bgColor instead
+ * @see \chillerlan\QRCode\QROptions::$bgColor
+ * @codeCoverageIgnore
+ */
+ protected function set_imagickBG(?string $imagickBG):void{
+ $this->bgColor = $imagickBG;
+ }
+
+ /**
+ * @deprecated 5.0.0 use QROptions::$bgColor instead
+ * @see \chillerlan\QRCode\QROptions::$bgColor
+ * @codeCoverageIgnore
+ */
+ protected function get_imagickBG():?string{
+ return $this->bgColor;
}
}