| 1 |
<?php |
| 2 |
|
| 3 |
/** |
| 4 |
* Class Decoder |
| 5 |
* |
| 6 |
* @created 17.01.2021 |
| 7 |
* @author ZXing Authors |
| 8 |
* @author Smiley <smiley@chillerlan.net> |
| 9 |
* @copyright 2021 Smiley |
| 10 |
* @license Apache-2.0 |
| 11 |
*/ |
| 12 |
namespace WCPOS\Vendor\chillerlan\QRCode\Decoder; |
| 13 |
|
| 14 |
use WCPOS\Vendor\chillerlan\QRCode\Common\BitBuffer; |
| 15 |
use WCPOS\Vendor\chillerlan\QRCode\Common\EccLevel; |
| 16 |
use WCPOS\Vendor\chillerlan\QRCode\Common\LuminanceSourceInterface; |
| 17 |
use WCPOS\Vendor\chillerlan\QRCode\Common\MaskPattern; |
| 18 |
use WCPOS\Vendor\chillerlan\QRCode\Common\Mode; |
| 19 |
use WCPOS\Vendor\chillerlan\QRCode\Common\Version; |
| 20 |
use WCPOS\Vendor\chillerlan\QRCode\Data\AlphaNum; |
| 21 |
use WCPOS\Vendor\chillerlan\QRCode\Data\Byte; |
| 22 |
use WCPOS\Vendor\chillerlan\QRCode\Data\ECI; |
| 23 |
use WCPOS\Vendor\chillerlan\QRCode\Data\Hanzi; |
| 24 |
use WCPOS\Vendor\chillerlan\QRCode\Data\Kanji; |
| 25 |
use WCPOS\Vendor\chillerlan\QRCode\Data\Number; |
| 26 |
use WCPOS\Vendor\chillerlan\QRCode\Detector\Detector; |
| 27 |
use Throwable; |
| 28 |
use function chr, str_replace; |
| 29 |
/** |
| 30 |
* The main class which implements QR Code decoding -- as opposed to locating and extracting |
| 31 |
* the QR Code from an image. |
| 32 |
* |
| 33 |
* @author Sean Owen |
| 34 |
*/ |
| 35 |
final class Decoder |
| 36 |
{ |
| 37 |
private ?Version $version = null; |
| 38 |
private ?EccLevel $eccLevel = null; |
| 39 |
private ?MaskPattern $maskPattern = null; |
| 40 |
private BitBuffer $bitBuffer; |
| 41 |
private Detector $detector; |
| 42 |
/** |
| 43 |
* Decodes a QR Code represented as a BitMatrix. |
| 44 |
* A 1 or "true" is taken to mean a black module. |
| 45 |
* |
| 46 |
* @throws \Throwable|\chillerlan\QRCode\Decoder\QRCodeDecoderException |
| 47 |
*/ |
| 48 |
public function decode(LuminanceSourceInterface $source) : DecoderResult |
| 49 |
{ |
| 50 |
$this->detector = new Detector($source); |
| 51 |
$matrix = $this->detector->detect(); |
| 52 |
try { |
| 53 |
// clone the BitMatrix to avoid errors in case we run into mirroring |
| 54 |
return $this->decodeMatrix(clone $matrix); |
| 55 |
} catch (Throwable $e) { |
| 56 |
try { |
| 57 |
/* |
| 58 |
* Prepare for a mirrored reading. |
| 59 |
* |
| 60 |
* Since we're here, this means we have successfully detected some kind |
| 61 |
* of version and format information when mirrored. This is a good sign, |
| 62 |
* that the QR code may be mirrored, and we should try once more with a |
| 63 |
* mirrored content. |
| 64 |
*/ |
| 65 |
return $this->decodeMatrix($matrix->resetVersionInfo()->mirrorDiagonal()); |
| 66 |
} catch (Throwable $f) { |
| 67 |
// Throw the exception from the original reading |
| 68 |
throw $e; |
| 69 |
} |
| 70 |
} |
| 71 |
} |
| 72 |
/** |
| 73 |
* @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException |
| 74 |
*/ |
| 75 |
private function decodeMatrix(BitMatrix $matrix) : DecoderResult |
| 76 |
{ |
| 77 |
// Read raw codewords |
| 78 |
$rawCodewords = $matrix->readCodewords(); |
| 79 |
$this->version = $matrix->getVersion(); |
| 80 |
$this->eccLevel = $matrix->getEccLevel(); |
| 81 |
$this->maskPattern = $matrix->getMaskPattern(); |
| 82 |
if ($this->version === null || $this->eccLevel === null || $this->maskPattern === null) { |
| 83 |
throw new QRCodeDecoderException('unable to read version or format info'); |
| 84 |
// @codeCoverageIgnore |
| 85 |
} |
| 86 |
$resultBytes = (new ReedSolomonDecoder($this->version, $this->eccLevel))->decode($rawCodewords); |
| 87 |
return $this->decodeBitStream($resultBytes); |
| 88 |
} |
| 89 |
/** |
| 90 |
* Decode the contents of that stream of bytes |
| 91 |
* |
| 92 |
* @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException |
| 93 |
*/ |
| 94 |
private function decodeBitStream(BitBuffer $bitBuffer) : DecoderResult |
| 95 |
{ |
| 96 |
$this->bitBuffer = $bitBuffer; |
| 97 |
$versionNumber = $this->version->getVersionNumber(); |
| 98 |
$symbolSequence = -1; |
| 99 |
$parityData = -1; |
| 100 |
$fc1InEffect = \false; |
| 101 |
$result = ''; |
| 102 |
// While still another segment to read... |
| 103 |
while ($this->bitBuffer->available() >= 4) { |
| 104 |
$datamode = $this->bitBuffer->read(4); |
| 105 |
// mode is encoded by 4 bits |
| 106 |
// OK, assume we're done |
| 107 |
if ($datamode === Mode::TERMINATOR) { |
| 108 |
break; |
| 109 |
} elseif ($datamode === Mode::NUMBER) { |
| 110 |
$result .= Number::decodeSegment($this->bitBuffer, $versionNumber); |
| 111 |
} elseif ($datamode === Mode::ALPHANUM) { |
| 112 |
$result .= $this->decodeAlphanumSegment($versionNumber, $fc1InEffect); |
| 113 |
} elseif ($datamode === Mode::BYTE) { |
| 114 |
$result .= Byte::decodeSegment($this->bitBuffer, $versionNumber); |
| 115 |
} elseif ($datamode === Mode::KANJI) { |
| 116 |
$result .= Kanji::decodeSegment($this->bitBuffer, $versionNumber); |
| 117 |
} elseif ($datamode === Mode::STRCTURED_APPEND) { |
| 118 |
if ($this->bitBuffer->available() < 16) { |
| 119 |
throw new QRCodeDecoderException('structured append: not enough bits left'); |
| 120 |
} |
| 121 |
// sequence number and parity is added later to the result metadata |
| 122 |
// Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue |
| 123 |
$symbolSequence = $this->bitBuffer->read(8); |
| 124 |
$parityData = $this->bitBuffer->read(8); |
| 125 |
} elseif ($datamode === Mode::FNC1_FIRST || $datamode === Mode::FNC1_SECOND) { |
| 126 |
// We do little with FNC1 except alter the parsed result a bit according to the spec |
| 127 |
$fc1InEffect = \true; |
| 128 |
} elseif ($datamode === Mode::ECI) { |
| 129 |
$result .= ECI::decodeSegment($this->bitBuffer, $versionNumber); |
| 130 |
} elseif ($datamode === Mode::HANZI) { |
| 131 |
$result .= Hanzi::decodeSegment($this->bitBuffer, $versionNumber); |
| 132 |
} else { |
| 133 |
throw new QRCodeDecoderException('invalid data mode'); |
| 134 |
} |
| 135 |
} |
| 136 |
return new DecoderResult(['rawBytes' => $this->bitBuffer, 'data' => $result, 'version' => $this->version, 'eccLevel' => $this->eccLevel, 'finderPatterns' => $this->detector->getFinderPatterns(), 'maskPattern' => $this->maskPattern, 'structuredAppendParity' => $parityData, 'structuredAppendSequence' => $symbolSequence]); |
| 137 |
} |
| 138 |
/** |
| 139 |
* |
| 140 |
*/ |
| 141 |
private function decodeAlphanumSegment(int $versionNumber, bool $fc1InEffect) : string |
| 142 |
{ |
| 143 |
$str = AlphaNum::decodeSegment($this->bitBuffer, $versionNumber); |
| 144 |
// See section 6.4.8.1, 6.4.8.2 |
| 145 |
if ($fc1InEffect) { |
| 146 |
// ??? |
| 147 |
// We need to massage the result a bit if in an FNC1 mode: |
| 148 |
$str = str_replace(chr(0x1d), '%', $str); |
| 149 |
$str = str_replace('%%', '%', $str); |
| 150 |
} |
| 151 |
return $str; |
| 152 |
} |
| 153 |
} |
| 154 |
|