codemasher 5 лет назад
Родитель
Сommit
d58c2044f1

+ 254 - 0
src/Common/EccLevel.php

@@ -0,0 +1,254 @@
+<?php
+/**
+ * Class EccLevel
+ *
+ * @filesource   EccLevel.php
+ * @created      19.11.2020
+ * @package      chillerlan\QRCode\Common
+ * @author       smiley <smiley@chillerlan.net>
+ * @copyright    2020 smiley
+ * @license      MIT
+ */
+
+namespace chillerlan\QRCode\Common;
+
+use chillerlan\QRCode\QRCodeException;
+
+/**
+ */
+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%.
+
+	/**
+	 * References to the keys of the following tables:
+	 *
+	 * @see \chillerlan\QRCode\Common\Version::MAX_BITS
+	 * @see \chillerlan\QRCode\Common\EccLevel::RSBLOCKS
+	 * @see \chillerlan\QRCode\Data\QRMatrix::formatPattern
+	 *
+	 * @var int[]
+	 */
+	public const MODES = [
+		self::L => 0,
+		self::M => 1,
+		self::Q => 2,
+		self::H => 3,
+	];
+
+	/**
+	 * ISO/IEC 18004:2000 Tables 13-22
+	 *
+	 * @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]],
+	];
+
+	/**
+	 * 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 = [
+	//  v  => [    L,     M,     Q,     H]  // modules
+		1  => [  152,   128,   104,    72], //  21
+		2  => [  272,   224,   176,   128], //  25
+		3  => [  440,   352,   272,   208], //  29
+		4  => [  640,   512,   384,   288], //  33
+		5  => [  864,   688,   496,   368], //  37
+		6  => [ 1088,   864,   608,   480], //  41
+		7  => [ 1248,   992,   704,   528], //  45
+		8  => [ 1552,  1232,   880,   688], //  49
+		9  => [ 1856,  1456,  1056,   800], //  53
+		10 => [ 2192,  1728,  1232,   976], //  57
+		11 => [ 2592,  2032,  1440,  1120], //  61
+		12 => [ 2960,  2320,  1648,  1264], //  65
+		13 => [ 3424,  2672,  1952,  1440], //  69 NICE!
+		14 => [ 3688,  2920,  2088,  1576], //  73
+		15 => [ 4184,  3320,  2360,  1784], //  77
+		16 => [ 4712,  3624,  2600,  2024], //  81
+		17 => [ 5176,  4056,  2936,  2264], //  85
+		18 => [ 5768,  4504,  3176,  2504], //  89
+		19 => [ 6360,  5016,  3560,  2728], //  93
+		20 => [ 6888,  5352,  3880,  3080], //  97
+		21 => [ 7456,  5712,  4096,  3248], // 101
+		22 => [ 8048,  6256,  4544,  3536], // 105
+		23 => [ 8752,  6880,  4912,  3712], // 109
+		24 => [ 9392,  7312,  5312,  4112], // 113
+		25 => [10208,  8000,  5744,  4304], // 117
+		26 => [10960,  8496,  6032,  4768], // 121
+		27 => [11744,  9024,  6464,  5024], // 125
+		28 => [12248,  9544,  6968,  5288], // 129
+		29 => [13048, 10136,  7288,  5608], // 133
+		30 => [13880, 10984,  7880,  5960], // 137
+		31 => [14744, 11640,  8264,  6344], // 141
+		32 => [15640, 12328,  8920,  6760], // 145
+		33 => [16568, 13048,  9368,  7208], // 149
+		34 => [17528, 13800,  9848,  7688], // 153
+		35 => [18448, 14496, 10288,  7888], // 157
+		36 => [19472, 15312, 10832,  8432], // 161
+		37 => [20528, 15936, 11408,  8768], // 165
+		38 => [21616, 16816, 12016,  9136], // 169
+		39 => [22496, 17728, 12656,  9776], // 173
+		40 => [23648, 18672, 13328, 10208], // 177
+	];
+
+	/**
+	 * ISO/IEC 18004:2000 Section 8.9 - Format Information
+	 *
+	 * ECC level -> mask pattern
+	 *
+	 * @var int[][]
+	 */
+	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,
+		],
+	];
+
+	private int $eccLevel;
+
+	/**
+	 * @param int $eccLevel containing the two bits encoding a QR Code's error correction level
+	 *
+	 * @throws \chillerlan\QRCode\QRCodeException
+	 */
+	public function __construct(int $eccLevel){
+
+		if((0b11 & $eccLevel) !== $eccLevel){
+			throw new QRCodeException('invalid ECC level');
+		}
+
+		$this->eccLevel = $eccLevel;
+	}
+
+	public function getOrdinal():int{
+		return $this->eccLevel;
+	}
+
+	/**
+	 * returns ECC block information for the given $version and $eccLevel
+	 *
+	 * @return int[]
+	 * @throws \chillerlan\QRCode\QRCodeException
+	 */
+	public function getRSBlocks(int $version):array{
+
+		if($version < 1 || $version > 40){
+			throw new QRCodeException('invalid version');
+		}
+
+		return self::RSBLOCKS[$version][self::MODES[$this->eccLevel]];
+	}
+
+	/**
+	 * returns the format pattern for the given $eccLevel and $maskPattern
+	 *
+	 * @return int
+	 * @throws \chillerlan\QRCode\QRCodeException
+	 */
+	public function getformatPattern(int $maskPattern):int{
+
+		if((0b111 & $maskPattern) !== $maskPattern){
+			throw new QRCodeException('invalid mask pattern');
+		}
+
+		return self::formatPattern[self::MODES[$this->eccLevel]][$maskPattern];
+	}
+
+	public function getMaxBits():array{
+		return array_combine(
+			array_keys(self::MAX_BITS),
+			array_column(self::MAX_BITS, self::MODES[$this->eccLevel])
+		);
+	}
+
+}

+ 0 - 56
src/Common/Version.php

@@ -21,55 +21,6 @@ use function array_column, array_combine, array_keys;
  */
 class Version{
 
-	/**
-	 * 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 = [
-		//  v  => [    L,     M,     Q,     H]  // modules
-		1  => [  152,   128,   104,    72], //  21
-		2  => [  272,   224,   176,   128], //  25
-		3  => [  440,   352,   272,   208], //  29
-		4  => [  640,   512,   384,   288], //  33
-		5  => [  864,   688,   496,   368], //  37
-		6  => [ 1088,   864,   608,   480], //  41
-		7  => [ 1248,   992,   704,   528], //  45
-		8  => [ 1552,  1232,   880,   688], //  49
-		9  => [ 1856,  1456,  1056,   800], //  53
-		10 => [ 2192,  1728,  1232,   976], //  57
-		11 => [ 2592,  2032,  1440,  1120], //  61
-		12 => [ 2960,  2320,  1648,  1264], //  65
-		13 => [ 3424,  2672,  1952,  1440], //  69 NICE!
-		14 => [ 3688,  2920,  2088,  1576], //  73
-		15 => [ 4184,  3320,  2360,  1784], //  77
-		16 => [ 4712,  3624,  2600,  2024], //  81
-		17 => [ 5176,  4056,  2936,  2264], //  85
-		18 => [ 5768,  4504,  3176,  2504], //  89
-		19 => [ 6360,  5016,  3560,  2728], //  93
-		20 => [ 6888,  5352,  3880,  3080], //  97
-		21 => [ 7456,  5712,  4096,  3248], // 101
-		22 => [ 8048,  6256,  4544,  3536], // 105
-		23 => [ 8752,  6880,  4912,  3712], // 109
-		24 => [ 9392,  7312,  5312,  4112], // 113
-		25 => [10208,  8000,  5744,  4304], // 117
-		26 => [10960,  8496,  6032,  4768], // 121
-		27 => [11744,  9024,  6464,  5024], // 125
-		28 => [12248,  9544,  6968,  5288], // 129
-		29 => [13048, 10136,  7288,  5608], // 133
-		30 => [13880, 10984,  7880,  5960], // 137
-		31 => [14744, 11640,  8264,  6344], // 141
-		32 => [15640, 12328,  8920,  6760], // 145
-		33 => [16568, 13048,  9368,  7208], // 149
-		34 => [17528, 13800,  9848,  7688], // 153
-		35 => [18448, 14496, 10288,  7888], // 157
-		36 => [19472, 15312, 10832,  8432], // 161
-		37 => [20528, 15936, 11408,  8768], // 165
-		38 => [21616, 16816, 12016,  9136], // 169
-		39 => [22496, 17728, 12656,  9776], // 173
-		40 => [23648, 18672, 13328, 10208], // 177
-	];
-
 	/**
 	 * ISO/IEC 18004:2000 Annex E, Table E.1 - Row/column coordinates of center module of Alignment Patterns
 	 *
@@ -199,11 +150,4 @@ class Version{
 		return self::ALIGNMENT_PATTERN[$this->version];
 	}
 
-	public static function getMaxBitsForEcc(int $eccLevel):array{
-		return array_combine(
-			array_keys(self::MAX_BITS),
-			array_column(self::MAX_BITS, QRCode::ECC_MODES[$eccLevel])
-		);
-	}
-
 }

+ 7 - 52
src/Data/QRData.php

@@ -12,7 +12,7 @@
 
 namespace chillerlan\QRCode\Data;
 
-use chillerlan\QRCode\Common\{Mode, Version};
+use chillerlan\QRCode\Common\{EccLevel, Mode, Version};
 use chillerlan\QRCode\QRCode;
 use chillerlan\QRCode\Helpers\{BitBuffer, Polynomial};
 use chillerlan\Settings\SettingsContainerInterface;
@@ -24,54 +24,6 @@ use function array_fill, array_merge, count, max, range, sprintf;
  */
 class QRData{
 
-	/**
-	 * @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]],
-	];
-
 	/**
 	 * current QR Code version
 	 */
@@ -111,6 +63,8 @@ class QRData{
 	 */
 	protected BitBuffer $bitBuffer;
 
+	protected EccLevel $eccLevel;
+
 	/**
 	 * QRData constructor.
 	 *
@@ -120,7 +74,8 @@ class QRData{
 	public function __construct(SettingsContainerInterface $options, array $dataSegments = null){
 		$this->options       = $options;
 		$this->bitBuffer     = new BitBuffer;
-		$this->maxBitsForEcc = Version::getMaxBitsForEcc($this->options->eccLevel);
+		$this->eccLevel      = new EccLevel($this->options->eccLevel);
+		$this->maxBitsForEcc = $this->eccLevel->getMaxBits();
 
 		if(!empty($dataSegments)){
 			$this->setData($dataSegments);
@@ -154,7 +109,7 @@ class QRData{
 	 * returns a fresh matrix object with the data written for the given $maskPattern
 	 */
 	public function initMatrix(int $maskPattern, bool $test = null):QRMatrix{
-		return (new QRMatrix($this->version, $this->options->eccLevel))
+		return (new QRMatrix($this->version, $this->eccLevel))
 			->init($maskPattern, $test)
 			->mapData($this->maskECC(), $maskPattern)
 		;
@@ -270,7 +225,7 @@ class QRData{
 	 * @see http://www.thonky.com/qr-code-tutorial/error-correction-coding
 	 */
 	protected function maskECC():array{
-		[$l1, $l2, $b1, $b2] = $this::RSBLOCKS[$this->version->getVersionNumber()][QRCode::ECC_MODES[$this->options->eccLevel]];
+		[$l1, $l2, $b1, $b2] = $this->eccLevel->getRSBlocks($this->version->getVersionNumber());
 
 		$rsBlocks     = array_fill(0, $l1, [$b1, $b2]);
 		$rsCount      = $l1 + $l2;

+ 15 - 72
src/Data/QRMatrix.php

@@ -12,11 +12,11 @@
 
 namespace chillerlan\QRCode\Data;
 
-use chillerlan\QRCode\Common\Version;
+use chillerlan\QRCode\Common\{EccLevel, Version};
 use chillerlan\QRCode\QRCode;
 use Closure;
 
-use function array_fill, array_key_exists, array_push, array_unshift, count, floor, max, min, range;
+use function array_fill, array_push, array_unshift, count, floor, max, min, range;
 
 /**
  * Holds a numerical representation of the final QR Code;
@@ -53,61 +53,6 @@ final class QRMatrix{
 	/** @var int */
 	public const M_TEST       = 0xff;
 
-	/**
-	 * ISO/IEC 18004:2000 Section 8.9 - Format Information
-	 *
-	 * ECC level -> mask pattern
-	 *
-	 * @var int[][]
-	 */
-	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,
-		],
-	];
-
-	/**
-	 * the current ECC level
-	 */
-	protected int $eclevel;
-
 	/**
 	 * the used mask pattern, set via QRMatrix::mapData()
 	 */
@@ -125,6 +70,11 @@ final class QRMatrix{
 	 */
 	protected array $matrix;
 
+	/**
+	 * the current ECC level
+	 */
+	protected EccLevel $ecclevel;
+
 	/**
 	 * a Version instance
 	 */
@@ -132,17 +82,10 @@ final class QRMatrix{
 
 	/**
 	 * QRMatrix constructor.
-	 *
-	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
 	 */
-	public function __construct(Version $version, int $eclevel){
-
-		if(!array_key_exists($eclevel, QRCode::ECC_MODES)){
-			throw new QRCodeDataException('invalid ecc level');
-		}
-
+	public function __construct(Version $version, EccLevel $eclevel){
 		$this->version     = $version;
-		$this->eclevel     = $eclevel;
+		$this->ecclevel    = $eclevel;
 		$this->moduleCount = $this->version->getDimension();
 		$this->matrix      = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, $this::M_NULL));
 	}
@@ -189,15 +132,15 @@ final class QRMatrix{
 	/**
 	 * Returns the current version number
 	 */
-	public function version():int{
-		return $this->version->getVersionNumber();
+	public function version():Version{
+		return $this->version;
 	}
 
 	/**
 	 * Returns the current ECC level
 	 */
-	public function eccLevel():int{
-		return $this->eclevel;
+	public function eccLevel():EccLevel{
+		return $this->ecclevel;
 	}
 
 	/**
@@ -406,7 +349,7 @@ final class QRMatrix{
 	 * 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;
+		$bits = $this->ecclevel->getformatPattern($maskPattern);
 
 		for($i = 0; $i < 15; $i++){
 			$v = !$test && (($bits >> $i) & 1) === 1;
@@ -495,7 +438,7 @@ final class QRMatrix{
 	public function setLogoSpace(int $width, int $height, int $startX = null, int $startY = null):QRMatrix{
 
 		// for logos we operate in ECC H (30%) only
-		if($this->eclevel !== QRCode::ECC_H){
+		if($this->ecclevel->getOrdinal() !== EccLevel::H){
 			throw new QRCodeDataException('ECC level "H" required to add logo space');
 		}
 

+ 0 - 27
src/QRCode.php

@@ -37,33 +37,6 @@ class QRCode{
 	/** @var int */
 	public const MASK_PATTERN_AUTO  = -1;
 
-	// ISO/IEC 18004:2000 Tables 12, 25
-
-	/** @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%.
-
-	/**
-	 * References to the keys of the following tables:
-	 *
-	 * @see \chillerlan\QRCode\Common\Version::MAX_BITS
-	 * @see \chillerlan\QRCode\Data\QRData::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 */

+ 4 - 2
src/QROptionsTrait.php

@@ -14,6 +14,8 @@
 
 namespace chillerlan\QRCode;
 
+use chillerlan\QRCode\Common\EccLevel;
+
 use function array_values, count, in_array, is_numeric, max, min, sprintf, strtolower;
 
 /**
@@ -50,7 +52,7 @@ trait QROptionsTrait{
 	 *   - Q => 25%
 	 *   - H => 30%
 	 */
-	protected int $eccLevel = QRCode::ECC_L;
+	protected int $eccLevel = EccLevel::L;
 
 	/**
 	 * Mask Pattern to use
@@ -251,7 +253,7 @@ trait QROptionsTrait{
 	 */
 	protected function set_eccLevel(int $eccLevel):void{
 
-		if(!isset(QRCode::ECC_MODES[$eccLevel])){
+		if(!isset(EccLevel::MODES[$eccLevel])){
 			throw new QRCodeException(sprintf('Invalid error correct level: %s', $eccLevel));
 		}
 

+ 8 - 20
tests/Data/QRMatrixTest.php

@@ -12,12 +12,10 @@
 
 namespace chillerlan\QRCodeTest\Data;
 
-use chillerlan\QRCode\Common\Version;
-use chillerlan\QRCode\QRCode;
-use chillerlan\QRCode\QROptions;
+use chillerlan\QRCode\Common\{EccLevel, Version};
+use chillerlan\QRCode\{QRCode, QROptions};
 use chillerlan\QRCode\Data\{QRCodeDataException, QRMatrix};
 use PHPUnit\Framework\TestCase;
-use ReflectionClass;
 
 /**
  * Tests the QRMatix class
@@ -44,7 +42,7 @@ final class QRMatrixTest extends TestCase{
 	 * @internal
 	 */
 	protected function getMatrix(int $version):QRMatrix{
-		return  new QRMatrix(new Version($version), QRCode::ECC_L);
+		return  new QRMatrix(new Version($version), new EccLevel(EccLevel::L));
 	}
 
 	/**
@@ -54,16 +52,6 @@ final class QRMatrixTest extends TestCase{
 		$this::assertInstanceOf(QRMatrix::class, $this->matrix);
 	}
 
-	/**
-	 * Tests if an exception is thrown when an invalid ECC level was given
-	 */
-	public function testInvalidEccException():void{
-		$this->expectException(QRCodeDataException::class);
-		$this->expectExceptionMessage('invalid ecc level');
-
-		$this->matrix = new QRMatrix(new Version(1), 42);
-	}
-
 	/**
 	 * Tests if size() returns the actual matrix size/count
 	 */
@@ -75,14 +63,14 @@ final class QRMatrixTest extends TestCase{
 	 * Tests if version() returns the current (given) version
 	 */
 	public function testVersion():void{
-		$this::assertSame($this::version, $this->matrix->version());
+		$this::assertSame($this::version, $this->matrix->version()->getVersionNumber());
 	}
 
 	/**
 	 * Tests if eccLevel() returns the current (given) ECC level
 	 */
 	public function testECC():void{
-		$this::assertSame(QRCode::ECC_L, $this->matrix->eccLevel());
+		$this::assertSame(EccLevel::L, $this->matrix->eccLevel()->getOrdinal());
 	}
 
 	/**
@@ -303,7 +291,7 @@ final class QRMatrixTest extends TestCase{
 	public function testSetLogoSpaceOrientation():void{
 		$o = new QROptions;
 		$o->version      = 10;
-		$o->eccLevel     = QRCode::ECC_H;
+		$o->eccLevel     = EccLevel::H;
 		$o->addQuietzone = false;
 
 		$matrix = (new QRCode($o))->addByteSegment('testdata')->getMatrix();
@@ -322,7 +310,7 @@ final class QRMatrixTest extends TestCase{
 	public function testSetLogoSpacePosition():void{
 		$o = new QROptions;
 		$o->version       = 10;
-		$o->eccLevel      = QRCode::ECC_H;
+		$o->eccLevel      = EccLevel::H;
 		$o->addQuietzone  = true;
 		$o->quietzoneSize = 10;
 
@@ -360,7 +348,7 @@ final class QRMatrixTest extends TestCase{
 
 		$o = new QROptions;
 		$o->version  = 5;
-		$o->eccLevel = QRCode::ECC_H;
+		$o->eccLevel = EccLevel::H;
 
 		(new QRCode($o))->addByteSegment('testdata')->getMatrix()->setLogoSpace(50, 50);
 	}

+ 2 - 2
tests/Output/QRStringTest.php

@@ -13,7 +13,7 @@
 namespace chillerlan\QRCodeTest\Output;
 
 use chillerlan\QRCodeExamples\MyCustomOutput;
-use chillerlan\QRCode\{QRCode, QROptions};
+use chillerlan\QRCode\{Common\EccLevel, QRCode, QROptions};
 use chillerlan\QRCode\Output\{QROutputInterface, QRString};
 
 /**
@@ -63,7 +63,7 @@ class QRStringTest extends QROutputTestAbstract{
 	 */
 	public function testCustomOutput():void{
 		$this->options->version         = 5;
-		$this->options->eccLevel        = QRCode::ECC_L;
+		$this->options->eccLevel        = EccLevel::L;
 		$this->options->outputType      = QRCode::OUTPUT_CUSTOM;
 		$this->options->outputInterface = MyCustomOutput::class;