aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/chillerlan/php-qrcode/src/Data/QRData.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/chillerlan/php-qrcode/src/Data/QRData.php')
-rw-r--r--vendor/chillerlan/php-qrcode/src/Data/QRData.php263
1 files changed, 263 insertions, 0 deletions
diff --git a/vendor/chillerlan/php-qrcode/src/Data/QRData.php b/vendor/chillerlan/php-qrcode/src/Data/QRData.php
new file mode 100644
index 000000000..9d610b357
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Data/QRData.php
@@ -0,0 +1,263 @@
+<?php
+/**
+ * Class QRData
+ *
+ * @created 25.11.2015
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2015 Smiley
+ * @license MIT
+ */
+
+namespace chillerlan\QRCode\Data;
+
+use chillerlan\QRCode\Common\{BitBuffer, EccLevel, Mode, Version};
+use chillerlan\Settings\SettingsContainerInterface;
+use function count, sprintf;
+
+/**
+ * Processes the binary data and maps it on a QRMatrix which is then being returned
+ */
+final class QRData{
+
+ /**
+ * the options instance
+ *
+ * @var \chillerlan\Settings\SettingsContainerInterface|\chillerlan\QRCode\QROptions
+ */
+ private SettingsContainerInterface $options;
+
+ /**
+ * a BitBuffer instance
+ */
+ private BitBuffer $bitBuffer;
+
+ /**
+ * an EccLevel instance
+ */
+ private EccLevel $eccLevel;
+
+ /**
+ * current QR Code version
+ */
+ private Version $version;
+
+ /**
+ * @var \chillerlan\QRCode\Data\QRDataModeInterface[]
+ */
+ private array $dataSegments = [];
+
+ /**
+ * Max bits for the current ECC mode
+ *
+ * @var int[]
+ */
+ private array $maxBitsForEcc;
+
+ /**
+ * QRData constructor.
+ */
+ public function __construct(SettingsContainerInterface $options, array $dataSegments = []){
+ $this->options = $options;
+ $this->bitBuffer = new BitBuffer;
+ $this->eccLevel = new EccLevel($this->options->eccLevel);
+ $this->maxBitsForEcc = $this->eccLevel->getMaxBits();
+
+ $this->setData($dataSegments);
+ }
+
+ /**
+ * Sets the data string (internally called by the constructor)
+ *
+ * Subsequent calls will overwrite the current state - use the QRCode::add*Segement() method instead
+ *
+ * @param \chillerlan\QRCode\Data\QRDataModeInterface[] $dataSegments
+ */
+ public function setData(array $dataSegments):self{
+ $this->dataSegments = $dataSegments;
+ $this->version = $this->getMinimumVersion();
+
+ $this->bitBuffer->clear();
+ $this->writeBitBuffer();
+
+ return $this;
+ }
+
+ /**
+ * Returns the current BitBuffer instance
+ *
+ * @codeCoverageIgnore
+ */
+ public function getBitBuffer():BitBuffer{
+ return $this->bitBuffer;
+ }
+
+ /**
+ * Sets a BitBuffer object
+ *
+ * This can be used instead of setData(), however, the version auto-detection is not available in this case.
+ * The version needs to match the length bits range for the data mode the data has been encoded with,
+ * additionally the bit array needs to contain enough pad bits.
+ *
+ * @throws \chillerlan\QRCode\Data\QRCodeDataException
+ */
+ public function setBitBuffer(BitBuffer $bitBuffer):self{
+
+ if($this->options->version === Version::AUTO){
+ throw new QRCodeDataException('version auto detection is not available');
+ }
+
+ if($bitBuffer->getLength() === 0){
+ throw new QRCodeDataException('the given BitBuffer is empty');
+ }
+
+ $this->dataSegments = [];
+ $this->bitBuffer = $bitBuffer;
+ $this->version = new Version($this->options->version);
+
+ return $this;
+ }
+
+ /**
+ * returns a fresh matrix object with the data written and masked with the given $maskPattern
+ */
+ public function writeMatrix():QRMatrix{
+ return (new QRMatrix($this->version, $this->eccLevel))
+ ->initFunctionalPatterns()
+ ->writeCodewords($this->bitBuffer)
+ ;
+ }
+
+ /**
+ * estimates the total length of the several mode segments in order to guess the minimum version
+ *
+ * @throws \chillerlan\QRCode\Data\QRCodeDataException
+ */
+ public function estimateTotalBitLength():int{
+ $length = 0;
+
+ foreach($this->dataSegments as $segment){
+ // data length of the current segment
+ $length += $segment->getLengthInBits();
+ // +4 bits for the mode descriptor
+ $length += 4;
+ // Hanzi mode sets an additional 4 bit long subset identifier
+ if($segment instanceof Hanzi){
+ $length += 4;
+ }
+ }
+
+ $provisionalVersion = null;
+
+ foreach($this->maxBitsForEcc as $version => $maxBits){
+
+ if($length <= $maxBits){
+ $provisionalVersion = $version;
+ }
+
+ }
+
+ if($provisionalVersion !== null){
+
+ // add character count indicator bits for the provisional version
+ foreach($this->dataSegments as $segment){
+ $length += Mode::getLengthBitsForVersion($segment::DATAMODE, $provisionalVersion);
+ }
+
+ // it seems that in some cases the estimated total length is not 100% accurate,
+ // so we substract 4 bits from the total when not in mixed mode
+ if(count($this->dataSegments) <= 1){
+ $length -= 4;
+ }
+
+ // we've got a match!
+ // or let's see if there's a higher version number available
+ if($length <= $this->maxBitsForEcc[$provisionalVersion] || isset($this->maxBitsForEcc[($provisionalVersion + 1)])){
+ return $length;
+ }
+
+ }
+
+ throw new QRCodeDataException(sprintf('estimated data exceeds %d bits', $length));
+ }
+
+ /**
+ * returns the minimum version number for the given string
+ *
+ * @throws \chillerlan\QRCode\Data\QRCodeDataException
+ */
+ public function getMinimumVersion():Version{
+
+ if($this->options->version !== Version::AUTO){
+ return new Version($this->options->version);
+ }
+
+ $total = $this->estimateTotalBitLength();
+
+ // guess the version number within the given range
+ for($version = $this->options->versionMin; $version <= $this->options->versionMax; $version++){
+ if($total <= ($this->maxBitsForEcc[$version] - 4)){
+ return new Version($version);
+ }
+ }
+
+ // it's almost impossible to run into this one as $this::estimateTotalBitLength() would throw first
+ throw new QRCodeDataException('failed to guess minimum version'); // @codeCoverageIgnore
+ }
+
+ /**
+ * creates a BitBuffer and writes the string data to it
+ *
+ * @throws \chillerlan\QRCode\Data\QRCodeDataException on data overflow
+ */
+ private function writeBitBuffer():void{
+ $MAX_BITS = $this->eccLevel->getMaxBitsForVersion($this->version);
+
+ foreach($this->dataSegments as $segment){
+ $segment->write($this->bitBuffer, $this->version->getVersionNumber());
+ }
+
+ // overflow, likely caused due to invalid version setting
+ if($this->bitBuffer->getLength() > $MAX_BITS){
+ throw new QRCodeDataException(
+ sprintf('code length overflow. (%d > %d bit)', $this->bitBuffer->getLength(), $MAX_BITS)
+ );
+ }
+
+ // add terminator (ISO/IEC 18004:2000 Table 2)
+ if(($this->bitBuffer->getLength() + 4) <= $MAX_BITS){
+ $this->bitBuffer->put(Mode::TERMINATOR, 4);
+ }
+
+ // Padding: ISO/IEC 18004:2000 8.4.9 Bit stream to codeword conversion
+
+ // if the final codeword is not exactly 8 bits in length, it shall be made 8 bits long
+ // by the addition of padding bits with binary value 0
+ while(($this->bitBuffer->getLength() % 8) !== 0){
+
+ if($this->bitBuffer->getLength() === $MAX_BITS){
+ break;
+ }
+
+ $this->bitBuffer->putBit(false);
+ }
+
+ // The message bit stream shall then be extended to fill the data capacity of the symbol
+ // corresponding to the Version and Error Correction Level, by the addition of the Pad
+ // Codewords 11101100 and 00010001 alternately.
+ $alternate = false;
+
+ while(($this->bitBuffer->getLength() + 8) <= $MAX_BITS){
+ $this->bitBuffer->put(($alternate) ? 0b00010001 : 0b11101100, 8);
+
+ $alternate = !$alternate;
+ }
+
+ // In certain versions of symbol, it may be necessary to add 3, 4 or 7 Remainder Bits (all zeros)
+ // to the end of the message in order exactly to fill the symbol capacity
+ while($this->bitBuffer->getLength() <= $MAX_BITS){
+ $this->bitBuffer->putBit(false);
+ }
+
+ }
+
+}