Decoder.php 5.7 KB

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