* @copyright 2015 Smiley * @license MIT */ namespace chillerlan\QRCode\Common; use chillerlan\QRCode\QRCodeException; use function count, floor, min; /** * Holds the raw binary data */ final class BitBuffer{ /** * The buffer content * * @var int[] */ private array $buffer; /** * Length of the content (bits) */ private int $length; /** * Read count (bytes) */ private int $bytesRead = 0; /** * Read count (bits) */ private int $bitsRead = 0; /** * BitBuffer constructor. * * @param int[] $bytes */ public function __construct(array $bytes = []){ $this->buffer = $bytes; $this->length = count($this->buffer); } /** * appends a sequence of bits */ public function put(int $bits, int $length):self{ for($i = 0; $i < $length; $i++){ $this->putBit((($bits >> ($length - $i - 1)) & 1) === 1); } return $this; } /** * appends a single bit */ public function putBit(bool $bit):self{ $bufIndex = (int)floor($this->length / 8); if(count($this->buffer) <= $bufIndex){ $this->buffer[] = 0; } if($bit === true){ $this->buffer[$bufIndex] |= (0x80 >> ($this->length % 8)); } $this->length++; return $this; } /** * returns the current buffer length */ public function getLength():int{ return $this->length; } /** * returns the buffer content * * to debug: array_map(fn($v) => sprintf('%08b', $v), $bitBuffer->getBuffer()) */ public function getBuffer():array{ return $this->buffer; } /** * @return int number of bits that can be read successfully */ public function available():int{ return ((8 * ($this->length - $this->bytesRead)) - $this->bitsRead); } /** * @author Sean Owen, ZXing * * @param int $numBits number of bits to read * * @return int representing the bits read. The bits will appear as the least-significant bits of the int * @throws \chillerlan\QRCode\QRCodeException if numBits isn't in [1,32] or more than is available */ public function read(int $numBits):int{ if($numBits < 1 || $numBits > $this->available()){ throw new QRCodeException('invalid $numBits: '.$numBits); } $result = 0; // First, read remainder from current byte if($this->bitsRead > 0){ $bitsLeft = (8 - $this->bitsRead); $toRead = min($numBits, $bitsLeft); $bitsToNotRead = ($bitsLeft - $toRead); $mask = ((0xff >> (8 - $toRead)) << $bitsToNotRead); $result = (($this->buffer[$this->bytesRead] & $mask) >> $bitsToNotRead); $numBits -= $toRead; $this->bitsRead += $toRead; if($this->bitsRead === 8){ $this->bitsRead = 0; $this->bytesRead++; } } // Next read whole bytes if($numBits > 0){ while($numBits >= 8){ $result = (($result << 8) | ($this->buffer[$this->bytesRead] & 0xff)); $this->bytesRead++; $numBits -= 8; } // Finally read a partial byte if($numBits > 0){ $bitsToNotRead = (8 - $numBits); $mask = ((0xff >> $bitsToNotRead) << $bitsToNotRead); $result = (($result << $numBits) | (($this->buffer[$this->bytesRead] & $mask) >> $bitsToNotRead)); $this->bitsRead += $numBits; } } return $result; } /** * Clears the buffer and resets the stats */ public function clear():self{ $this->buffer = []; $this->length = 0; return $this->rewind(); } /** * Resets the read-counters */ public function rewind():self{ $this->bytesRead = 0; $this->bitsRead = 0; return $this; } }