Decoder.php 5.2 KB

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