QRData.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. <?php
  2. /**
  3. * Class QRData
  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, EccLevel, MaskPattern, Mode, Version};
  12. use chillerlan\QRCode\QRCode;
  13. use chillerlan\Settings\SettingsContainerInterface;
  14. use function sprintf;
  15. /**
  16. * Processes the binary data and maps it on a matrix which is then being returned
  17. */
  18. final class QRData{
  19. /**
  20. * the options instance
  21. *
  22. * @var \chillerlan\Settings\SettingsContainerInterface|\chillerlan\QRCode\QROptions
  23. */
  24. private SettingsContainerInterface $options;
  25. /**
  26. * a BitBuffer instance
  27. */
  28. private BitBuffer $bitBuffer;
  29. /**
  30. * an EccLevel instance
  31. */
  32. private EccLevel $eccLevel;
  33. /**
  34. * current QR Code version
  35. */
  36. private Version $version;
  37. /**
  38. * @var \chillerlan\QRCode\Data\QRDataModeInterface[]
  39. */
  40. private array $dataSegments = [];
  41. /**
  42. * Max bits for the current ECC mode
  43. *
  44. * @var int[]
  45. */
  46. private array $maxBitsForEcc;
  47. /**
  48. * QRData constructor.
  49. *
  50. * @param \chillerlan\Settings\SettingsContainerInterface $options
  51. * @param \chillerlan\QRCode\Data\QRDataModeInterface[]|null $dataSegments
  52. */
  53. public function __construct(SettingsContainerInterface $options, array $dataSegments = null){
  54. $this->options = $options;
  55. $this->bitBuffer = new BitBuffer;
  56. $this->eccLevel = new EccLevel($this->options->eccLevel);
  57. $this->maxBitsForEcc = $this->eccLevel->getMaxBits();
  58. if(!empty($dataSegments)){
  59. $this->setData($dataSegments);
  60. }
  61. }
  62. /**
  63. * Sets the data string (internally called by the constructor)
  64. */
  65. public function setData(array $dataSegments):self{
  66. $this->dataSegments = $dataSegments;
  67. $version = $this->options->version === QRCode::VERSION_AUTO
  68. ? $this->getMinimumVersion()
  69. : $this->options->version;
  70. $this->version = new Version($version);
  71. $this->writeBitBuffer();
  72. return $this;
  73. }
  74. /**
  75. * returns a fresh matrix object with the data written and masked with the given $maskPattern
  76. */
  77. public function writeMatrix(MaskPattern $maskPattern):QRMatrix{
  78. return (new QRMatrix($this->version, $this->eccLevel, $maskPattern))
  79. ->initFunctionalPatterns()
  80. ->writeCodewords($this->bitBuffer)
  81. ->mask()
  82. ;
  83. }
  84. /**
  85. * estimates the total length of the several mode segments in order to guess the minimum version
  86. *
  87. * @throws \chillerlan\QRCode\Data\QRCodeDataException
  88. */
  89. private function estimateTotalBitLength():int{
  90. $length = 0;
  91. $margin = 0;
  92. foreach($this->dataSegments as $segment){
  93. // data length in bits of the current segment +4 bits for each mode descriptor
  94. $length += ($segment->getLengthInBits() + Mode::getLengthBitsForMode($segment->getDataMode())[0] + 4);
  95. if(!$segment instanceof ECI){
  96. // mode length bits margin to the next breakpoint
  97. $margin += ($segment instanceof Byte ? 8 : 2);
  98. }
  99. }
  100. foreach([9, 26, 40] as $breakpoint){
  101. // length bits for the first breakpoint have already been added
  102. if($breakpoint > 9){
  103. $length += $margin;
  104. }
  105. if($length < $this->maxBitsForEcc[$breakpoint]){
  106. return $length;
  107. }
  108. }
  109. throw new QRCodeDataException(sprintf('estimated data exceeds %d bits', $length));
  110. }
  111. /**
  112. * returns the minimum version number for the given string
  113. *
  114. * @throws \chillerlan\QRCode\Data\QRCodeDataException
  115. */
  116. private function getMinimumVersion():int{
  117. $total = $this->estimateTotalBitLength();
  118. // guess the version number within the given range
  119. for($version = $this->options->versionMin; $version <= $this->options->versionMax; $version++){
  120. if($total <= $this->maxBitsForEcc[$version]){
  121. return $version;
  122. }
  123. }
  124. // it's almost impossible to run into this one as $this::estimateTotalBitLength() would throw first
  125. throw new QRCodeDataException('failed to guess minimum version'); // @codeCoverageIgnore
  126. }
  127. /**
  128. * creates a BitBuffer and writes the string data to it
  129. *
  130. * @throws \chillerlan\QRCode\QRCodeException on data overflow
  131. */
  132. private function writeBitBuffer():void{
  133. $version = $this->version->getVersionNumber();
  134. $MAX_BITS = $this->maxBitsForEcc[$version];
  135. foreach($this->dataSegments as $segment){
  136. $segment->write($this->bitBuffer, $version);
  137. }
  138. // overflow, likely caused due to invalid version setting
  139. if($this->bitBuffer->getLength() > $MAX_BITS){
  140. throw new QRCodeDataException(
  141. sprintf('code length overflow. (%d > %d bit)', $this->bitBuffer->getLength(), $MAX_BITS)
  142. );
  143. }
  144. // add terminator (ISO/IEC 18004:2000 Table 2)
  145. if($this->bitBuffer->getLength() + 4 <= $MAX_BITS){
  146. $this->bitBuffer->put(0b0000, 4);
  147. }
  148. // Padding: ISO/IEC 18004:2000 8.4.9 Bit stream to codeword conversion
  149. // if the final codeword is not exactly 8 bits in length, it shall be made 8 bits long
  150. // by the addition of padding bits with binary value 0
  151. while($this->bitBuffer->getLength() % 8 !== 0){
  152. $this->bitBuffer->putBit(false);
  153. }
  154. // The message bit stream shall then be extended to fill the data capacity of the symbol
  155. // corresponding to the Version and Error Correction Level, by the addition of the Pad
  156. // Codewords 11101100 and 00010001 alternately.
  157. $alternate = false;
  158. while(true){
  159. if($this->bitBuffer->getLength() >= $MAX_BITS){
  160. break;
  161. }
  162. $this->bitBuffer->put($alternate ? 0b00010001 : 0b11101100, 8);
  163. $alternate = !$alternate;
  164. }
  165. }
  166. }