Number.php 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. <?php
  2. /**
  3. * Class Number
  4. *
  5. * @created 26.11.2015
  6. * @author Smiley <smiley@chillerlan.net>
  7. * @copyright 2015 Smiley
  8. * @license MIT
  9. */
  10. namespace chillerlan\QRCode\Data;
  11. use chillerlan\QRCode\Common\{BitBuffer, Mode};
  12. use function ceil, intdiv, substr, unpack;
  13. /**
  14. * Numeric mode: decimal digits 0 to 9
  15. *
  16. * ISO/IEC 18004:2000 Section 8.3.2
  17. * ISO/IEC 18004:2000 Section 8.4.2
  18. */
  19. final class Number extends QRDataModeAbstract{
  20. /**
  21. * @inheritDoc
  22. */
  23. public const DATAMODE = Mode::NUMBER;
  24. /**
  25. * @inheritDoc
  26. */
  27. public function getLengthInBits():int{
  28. return (int)ceil($this->getCharCount() * (10 / 3));
  29. }
  30. /**
  31. * @inheritDoc
  32. */
  33. public static function validateString(string $string):bool{
  34. return (bool)preg_match('/^\d+$/', $string);
  35. }
  36. /**
  37. * @inheritDoc
  38. */
  39. public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{
  40. $len = $this->getCharCount();
  41. $bitBuffer
  42. ->put(self::DATAMODE, 4)
  43. ->put($len, $this::getLengthBits($versionNumber))
  44. ;
  45. $i = 0;
  46. // encode numeric triplets in 10 bits
  47. while(($i + 2) < $len){
  48. $bitBuffer->put($this->parseInt(substr($this->data, $i, 3)), 10);
  49. $i += 3;
  50. }
  51. if($i < $len){
  52. // encode 2 remaining numbers in 7 bits
  53. if(($len - $i) === 2){
  54. $bitBuffer->put($this->parseInt(substr($this->data, $i, 2)), 7);
  55. }
  56. // encode one remaining number in 4 bits
  57. elseif(($len - $i) === 1){
  58. $bitBuffer->put($this->parseInt(substr($this->data, $i, 1)), 4);
  59. }
  60. }
  61. return $this;
  62. }
  63. /**
  64. * get the code for the given numeric string
  65. *
  66. * @throws \chillerlan\QRCode\Data\QRCodeDataException
  67. */
  68. private function parseInt(string $string):int{
  69. $num = 0;
  70. $ords = unpack('C*', $string);
  71. if($ords === false){
  72. throw new QRCodeDataException('unpack() error');
  73. }
  74. foreach($ords as $ord){
  75. $num = ($num * 10 + $ord - 48);
  76. }
  77. return $num;
  78. }
  79. /**
  80. * @inheritDoc
  81. *
  82. * @throws \chillerlan\QRCode\Data\QRCodeDataException
  83. */
  84. public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{
  85. $length = $bitBuffer->read(self::getLengthBits($versionNumber));
  86. $result = '';
  87. // Read three digits at a time
  88. while($length >= 3){
  89. // Each 10 bits encodes three digits
  90. if($bitBuffer->available() < 10){
  91. throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
  92. }
  93. $threeDigitsBits = $bitBuffer->read(10);
  94. if($threeDigitsBits >= 1000){
  95. throw new QRCodeDataException('error decoding numeric value');
  96. }
  97. $result .= intdiv($threeDigitsBits, 100);
  98. $result .= (intdiv($threeDigitsBits, 10) % 10);
  99. $result .= ($threeDigitsBits % 10);
  100. $length -= 3;
  101. }
  102. if($length === 2){
  103. // Two digits left over to read, encoded in 7 bits
  104. if($bitBuffer->available() < 7){
  105. throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
  106. }
  107. $twoDigitsBits = $bitBuffer->read(7);
  108. if($twoDigitsBits >= 100){
  109. throw new QRCodeDataException('error decoding numeric value');
  110. }
  111. $result .= intdiv($twoDigitsBits, 10);
  112. $result .= ($twoDigitsBits % 10);
  113. }
  114. elseif($length === 1){
  115. // One digit left over to read
  116. if($bitBuffer->available() < 4){
  117. throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
  118. }
  119. $digitBits = $bitBuffer->read(4);
  120. if($digitBits >= 10){
  121. throw new QRCodeDataException('error decoding numeric value');
  122. }
  123. $result .= $digitBits;
  124. }
  125. return $result;
  126. }
  127. }