BitArray.php
11 months ago
BitMatrix.php
11 months ago
BitUtils.php
11 months ago
CharacterSetEci.php
11 months ago
EcBlock.php
11 months ago
EcBlocks.php
11 months ago
ErrorCorrectionLevel.php
11 months ago
FormatInformation.php
11 months ago
Mode.php
11 months ago
ReedSolomonCodec.php
11 months ago
Version.php
11 months ago
FormatInformation.php
133 lines
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * BaconQrCode |
| 5 | * |
| 6 | * @link http://github.com/Bacon/BaconQrCode For the canonical source repository |
| 7 | * @copyright 2013 Ben 'DASPRiD' Scholzen |
| 8 | * @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License |
| 9 | */ |
| 10 | namespace WP2FA_Vendor\BaconQrCode\Common; |
| 11 | |
| 12 | /** |
| 13 | * Encapsulates a QR Code's format information, including the data mask used and error correction level. |
| 14 | */ |
| 15 | class FormatInformation |
| 16 | { |
| 17 | /** |
| 18 | * Mask for format information. |
| 19 | */ |
| 20 | private const FORMAT_INFO_MASK_QR = 0x5412; |
| 21 | /** |
| 22 | * Lookup table for decoding format information. |
| 23 | * |
| 24 | * See ISO 18004:2006, Annex C, Table C.1 |
| 25 | */ |
| 26 | private const FORMAT_INFO_DECODE_LOOKUP = [[0x5412, 0x0], [0x5125, 0x1], [0x5e7c, 0x2], [0x5b4b, 0x3], [0x45f9, 0x4], [0x40ce, 0x5], [0x4f97, 0x6], [0x4aa0, 0x7], [0x77c4, 0x8], [0x72f3, 0x9], [0x7daa, 0xa], [0x789d, 0xb], [0x662f, 0xc], [0x6318, 0xd], [0x6c41, 0xe], [0x6976, 0xf], [0x1689, 0x10], [0x13be, 0x11], [0x1ce7, 0x12], [0x19d0, 0x13], [0x762, 0x14], [0x255, 0x15], [0xd0c, 0x16], [0x83b, 0x17], [0x355f, 0x18], [0x3068, 0x19], [0x3f31, 0x1a], [0x3a06, 0x1b], [0x24b4, 0x1c], [0x2183, 0x1d], [0x2eda, 0x1e], [0x2bed, 0x1f]]; |
| 27 | /** |
| 28 | * Offset i holds the number of 1 bits in the binary representation of i. |
| 29 | * |
| 30 | * @var int[] |
| 31 | */ |
| 32 | private const BITS_SET_IN_HALF_BYTE = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4]; |
| 33 | /** |
| 34 | * Error correction level. |
| 35 | * |
| 36 | * @var ErrorCorrectionLevel |
| 37 | */ |
| 38 | private $ecLevel; |
| 39 | /** |
| 40 | * Data mask. |
| 41 | * |
| 42 | * @var int |
| 43 | */ |
| 44 | private $dataMask; |
| 45 | protected function __construct(int $formatInfo) |
| 46 | { |
| 47 | $this->ecLevel = ErrorCorrectionLevel::forBits($formatInfo >> 3 & 0x3); |
| 48 | $this->dataMask = $formatInfo & 0x7; |
| 49 | } |
| 50 | /** |
| 51 | * Checks how many bits are different between two integers. |
| 52 | */ |
| 53 | public static function numBitsDiffering(int $a, int $b) : int |
| 54 | { |
| 55 | $a ^= $b; |
| 56 | return self::BITS_SET_IN_HALF_BYTE[$a & 0xf] + self::BITS_SET_IN_HALF_BYTE[BitUtils::unsignedRightShift($a, 4) & 0xf] + self::BITS_SET_IN_HALF_BYTE[BitUtils::unsignedRightShift($a, 8) & 0xf] + self::BITS_SET_IN_HALF_BYTE[BitUtils::unsignedRightShift($a, 12) & 0xf] + self::BITS_SET_IN_HALF_BYTE[BitUtils::unsignedRightShift($a, 16) & 0xf] + self::BITS_SET_IN_HALF_BYTE[BitUtils::unsignedRightShift($a, 20) & 0xf] + self::BITS_SET_IN_HALF_BYTE[BitUtils::unsignedRightShift($a, 24) & 0xf] + self::BITS_SET_IN_HALF_BYTE[BitUtils::unsignedRightShift($a, 28) & 0xf]; |
| 57 | } |
| 58 | /** |
| 59 | * Decodes format information. |
| 60 | */ |
| 61 | public static function decodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self |
| 62 | { |
| 63 | $formatInfo = self::doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2); |
| 64 | if (null !== $formatInfo) { |
| 65 | return $formatInfo; |
| 66 | } |
| 67 | // Should return null, but, some QR codes apparently do not mask this info. Try again by actually masking the |
| 68 | // pattern first. |
| 69 | return self::doDecodeFormatInformation($maskedFormatInfo1 ^ self::FORMAT_INFO_MASK_QR, $maskedFormatInfo2 ^ self::FORMAT_INFO_MASK_QR); |
| 70 | } |
| 71 | /** |
| 72 | * Internal method for decoding format information. |
| 73 | */ |
| 74 | private static function doDecodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self |
| 75 | { |
| 76 | $bestDifference = \PHP_INT_MAX; |
| 77 | $bestFormatInfo = 0; |
| 78 | foreach (self::FORMAT_INFO_DECODE_LOOKUP as $decodeInfo) { |
| 79 | $targetInfo = $decodeInfo[0]; |
| 80 | if ($targetInfo === $maskedFormatInfo1 || $targetInfo === $maskedFormatInfo2) { |
| 81 | // Found an exact match |
| 82 | return new self($decodeInfo[1]); |
| 83 | } |
| 84 | $bitsDifference = self::numBitsDiffering($maskedFormatInfo1, $targetInfo); |
| 85 | if ($bitsDifference < $bestDifference) { |
| 86 | $bestFormatInfo = $decodeInfo[1]; |
| 87 | $bestDifference = $bitsDifference; |
| 88 | } |
| 89 | if ($maskedFormatInfo1 !== $maskedFormatInfo2) { |
| 90 | // Also try the other option |
| 91 | $bitsDifference = self::numBitsDiffering($maskedFormatInfo2, $targetInfo); |
| 92 | if ($bitsDifference < $bestDifference) { |
| 93 | $bestFormatInfo = $decodeInfo[1]; |
| 94 | $bestDifference = $bitsDifference; |
| 95 | } |
| 96 | } |
| 97 | } |
| 98 | // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match. |
| 99 | if ($bestDifference <= 3) { |
| 100 | return new self($bestFormatInfo); |
| 101 | } |
| 102 | return null; |
| 103 | } |
| 104 | /** |
| 105 | * Returns the error correction level. |
| 106 | */ |
| 107 | public function getErrorCorrectionLevel() : ErrorCorrectionLevel |
| 108 | { |
| 109 | return $this->ecLevel; |
| 110 | } |
| 111 | /** |
| 112 | * Returns the data mask. |
| 113 | */ |
| 114 | public function getDataMask() : int |
| 115 | { |
| 116 | return $this->dataMask; |
| 117 | } |
| 118 | /** |
| 119 | * Hashes the code of the EC level. |
| 120 | */ |
| 121 | public function hashCode() : int |
| 122 | { |
| 123 | return $this->ecLevel->getBits() << 3 | $this->dataMask; |
| 124 | } |
| 125 | /** |
| 126 | * Verifies if this instance equals another one. |
| 127 | */ |
| 128 | public function equals(self $other) : bool |
| 129 | { |
| 130 | return $this->ecLevel === $other->ecLevel && $this->dataMask === $other->dataMask; |
| 131 | } |
| 132 | } |
| 133 |