BitBuffer.php 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. <?php
  2. /**
  3. * Class BitBuffer
  4. *
  5. * @created 25.11.2015
  6. * @author Smiley <smiley@chillerlan.net>
  7. * @copyright 2015 Smiley
  8. * @license MIT
  9. */
  10. namespace chillerlan\QRCode\Common;
  11. use InvalidArgumentException;
  12. use function count, floor;
  13. /**
  14. * Holds the raw binary data
  15. */
  16. final class BitBuffer{
  17. /**
  18. * The buffer content
  19. *
  20. * @var int[]
  21. */
  22. private array $buffer;
  23. /**
  24. * Length of the content (bits)
  25. */
  26. private int $length;
  27. private int $bytesRead = 0;
  28. private int $bitsRead = 0;
  29. /**
  30. * BitBuffer constructor.
  31. */
  32. public function __construct(array $bytes = null){
  33. $this->buffer = $bytes ?? [];
  34. $this->length = count($this->buffer);
  35. }
  36. /**
  37. * clears the buffer
  38. */
  39. public function clear():BitBuffer{
  40. $this->buffer = [];
  41. $this->length = 0;
  42. return $this;
  43. }
  44. /**
  45. * appends a sequence of bits
  46. */
  47. public function put(int $num, int $length):BitBuffer{
  48. for($i = 0; $i < $length; $i++){
  49. $this->putBit((($num >> ($length - $i - 1)) & 1) === 1);
  50. }
  51. return $this;
  52. }
  53. /**
  54. * appends a single bit
  55. */
  56. public function putBit(bool $bit):BitBuffer{
  57. $bufIndex = (int)floor($this->length / 8);
  58. if(count($this->buffer) <= $bufIndex){
  59. $this->buffer[] = 0;
  60. }
  61. if($bit === true){
  62. $this->buffer[$bufIndex] |= (0x80 >> ($this->length % 8));
  63. }
  64. $this->length++;
  65. return $this;
  66. }
  67. /**
  68. * returns the current buffer length
  69. */
  70. public function getLength():int{
  71. return $this->length;
  72. }
  73. /**
  74. * returns the buffer content
  75. */
  76. public function getBuffer():array{
  77. return $this->buffer;
  78. }
  79. /**
  80. * @return int number of bits that can be read successfully
  81. */
  82. public function available():int{
  83. return 8 * ($this->length - $this->bytesRead) - $this->bitsRead;
  84. }
  85. /**
  86. * @author Sean Owen, ZXing
  87. *
  88. * @param int $numBits number of bits to read
  89. *
  90. * @return int representing the bits read. The bits will appear as the least-significant
  91. * bits of the int
  92. * @throws InvalidArgumentException if numBits isn't in [1,32] or more than is available
  93. */
  94. public function read(int $numBits):int{
  95. if($numBits < 1 || $numBits > 32 || $numBits > $this->available()){
  96. throw new InvalidArgumentException('invalid $numBits: '.$numBits);
  97. }
  98. $result = 0;
  99. // First, read remainder from current byte
  100. if($this->bitsRead > 0){
  101. $bitsLeft = 8 - $this->bitsRead;
  102. $toRead = $numBits < $bitsLeft ? $numBits : $bitsLeft;
  103. $bitsToNotRead = $bitsLeft - $toRead;
  104. $mask = (0xff >> (8 - $toRead)) << $bitsToNotRead;
  105. $result = ($this->buffer[$this->bytesRead] & $mask) >> $bitsToNotRead;
  106. $numBits -= $toRead;
  107. $this->bitsRead += $toRead;
  108. if($this->bitsRead == 8){
  109. $this->bitsRead = 0;
  110. $this->bytesRead++;
  111. }
  112. }
  113. // Next read whole bytes
  114. if($numBits > 0){
  115. while($numBits >= 8){
  116. $result = ($result << 8) | ($this->buffer[$this->bytesRead] & 0xff);
  117. $this->bytesRead++;
  118. $numBits -= 8;
  119. }
  120. // Finally read a partial byte
  121. if($numBits > 0){
  122. $bitsToNotRead = 8 - $numBits;
  123. $mask = (0xff >> $bitsToNotRead) << $bitsToNotRead;
  124. $result = ($result << $numBits) | (($this->buffer[$this->bytesRead] & $mask) >> $bitsToNotRead);
  125. $this->bitsRead += $numBits;
  126. }
  127. }
  128. return $result;
  129. }
  130. }