AlphaNum.php 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. <?php
  2. /**
  3. * Class AlphaNum
  4. *
  5. * @created 25.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, preg_match, strpos;
  13. /**
  14. * Alphanumeric mode: 0 to 9, A to Z, space, $ % * + - . / :
  15. *
  16. * ISO/IEC 18004:2000 Section 8.3.3
  17. * ISO/IEC 18004:2000 Section 8.4.3
  18. */
  19. final class AlphaNum extends QRDataModeAbstract{
  20. /**
  21. * ISO/IEC 18004:2000 Table 5
  22. *
  23. * @var string
  24. */
  25. private const CHAR_MAP = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
  26. /**
  27. * @inheritDoc
  28. */
  29. public const DATAMODE = Mode::ALPHANUM;
  30. /**
  31. * @inheritDoc
  32. */
  33. public function getLengthInBits():int{
  34. return (int)ceil($this->getCharCount() * (11 / 2));
  35. }
  36. /**
  37. * @inheritDoc
  38. */
  39. public static function validateString(string $string):bool{
  40. return (bool)preg_match('/^[A-Z\d %$*+-.:\/]+$/', $string);
  41. }
  42. /**
  43. * @inheritDoc
  44. */
  45. public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{
  46. $len = $this->getCharCount();
  47. $bitBuffer
  48. ->put(self::DATAMODE, 4)
  49. ->put($len, $this::getLengthBits($versionNumber))
  50. ;
  51. // encode 2 characters in 11 bits
  52. for($i = 0; ($i + 1) < $len; $i += 2){
  53. $bitBuffer->put(
  54. ($this->ord($this->data[$i]) * 45 + $this->ord($this->data[($i + 1)])),
  55. 11,
  56. );
  57. }
  58. // encode a remaining character in 6 bits
  59. if($i < $len){
  60. $bitBuffer->put($this->ord($this->data[$i]), 6);
  61. }
  62. return $this;
  63. }
  64. /**
  65. * @inheritDoc
  66. *
  67. * @throws \chillerlan\QRCode\Data\QRCodeDataException
  68. */
  69. public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{
  70. $length = $bitBuffer->read(self::getLengthBits($versionNumber));
  71. $result = '';
  72. // Read two characters at a time
  73. while($length > 1){
  74. if($bitBuffer->available() < 11){
  75. throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
  76. }
  77. $nextTwoCharsBits = $bitBuffer->read(11);
  78. $result .= self::chr(intdiv($nextTwoCharsBits, 45));
  79. $result .= self::chr($nextTwoCharsBits % 45);
  80. $length -= 2;
  81. }
  82. if($length === 1){
  83. // special case: one character left
  84. if($bitBuffer->available() < 6){
  85. throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
  86. }
  87. $result .= self::chr($bitBuffer->read(6));
  88. }
  89. return $result;
  90. }
  91. /**
  92. * @throws \chillerlan\QRCode\Data\QRCodeDataException
  93. */
  94. private function ord(string $chr):int{
  95. /** @phan-suppress-next-line PhanParamSuspiciousOrder */
  96. $ord = strpos(self::CHAR_MAP, $chr);
  97. if($ord === false){
  98. throw new QRCodeDataException('invalid character'); // @codeCoverageIgnore
  99. }
  100. return $ord;
  101. }
  102. /**
  103. * @throws \chillerlan\QRCode\Data\QRCodeDataException
  104. */
  105. private static function chr(int $ord):string{
  106. if($ord < 0 || $ord > 44){
  107. throw new QRCodeDataException('invalid character code'); // @codeCoverageIgnore
  108. }
  109. return self::CHAR_MAP[$ord];
  110. }
  111. }