QRDataAbstract.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. <?php
  2. /**
  3. * Class QRDataAbstract
  4. *
  5. * @filesource QRDataAbstract.php
  6. * @created 25.11.2015
  7. * @package chillerlan\QRCode\Data
  8. * @author Smiley <smiley@chillerlan.net>
  9. * @copyright 2015 Smiley
  10. * @license MIT
  11. */
  12. namespace chillerlan\QRCode\Data;
  13. use chillerlan\QRCode\{QRCode, QRCodeException};
  14. use chillerlan\QRCode\Helpers\{BitBuffer, Polynomial};
  15. use chillerlan\Settings\SettingsContainerInterface;
  16. use function array_fill, array_merge, count, max, mb_convert_encoding, mb_detect_encoding, range, sprintf, strlen;
  17. /**
  18. * Processes the binary data and maps it on a matrix which is then being returned
  19. */
  20. abstract class QRDataAbstract implements QRDataInterface{
  21. /**
  22. * the string byte count
  23. *
  24. * @var int
  25. */
  26. protected $strlen;
  27. /**
  28. * the current data mode: Num, Alphanum, Kanji, Byte
  29. *
  30. * @var int
  31. */
  32. protected $datamode;
  33. /**
  34. * mode length bits for the version breakpoints 1-9, 10-26 and 27-40
  35. *
  36. * @var array
  37. */
  38. protected $lengthBits = [0, 0, 0];
  39. /**
  40. * current QR Code version
  41. *
  42. * @var int
  43. */
  44. protected $version;
  45. /**
  46. * the raw data that's being passed to QRMatrix::mapData()
  47. *
  48. * @var array
  49. */
  50. protected $matrixdata;
  51. /**
  52. * ECC temp data
  53. *
  54. * @var array
  55. */
  56. protected $ecdata;
  57. /**
  58. * ECC temp data
  59. *
  60. * @var array
  61. */
  62. protected $dcdata;
  63. /**
  64. * @var \chillerlan\QRCode\QROptions
  65. */
  66. protected $options;
  67. /**
  68. * @var \chillerlan\QRCode\Helpers\BitBuffer
  69. */
  70. protected $bitBuffer;
  71. /**
  72. * QRDataInterface constructor.
  73. *
  74. * @param \chillerlan\Settings\SettingsContainerInterface $options
  75. * @param string|null $data
  76. */
  77. public function __construct(SettingsContainerInterface $options, string $data = null){
  78. $this->options = $options;
  79. if($data !== null){
  80. $this->setData($data);
  81. }
  82. }
  83. /**
  84. * Sets the data string (internally called by the constructor)
  85. *
  86. * @param string $data
  87. *
  88. * @return \chillerlan\QRCode\Data\QRDataInterface
  89. */
  90. public function setData(string $data):QRDataInterface{
  91. if($this->datamode === QRCode::DATA_KANJI){
  92. $data = mb_convert_encoding($data, 'SJIS', mb_detect_encoding($data));
  93. }
  94. $this->strlen = $this->getLength($data);
  95. $this->version = $this->options->version === QRCode::VERSION_AUTO
  96. ? $this->getMinimumVersion()
  97. : $this->options->version;
  98. $this->matrixdata = $this
  99. ->writeBitBuffer($data)
  100. ->maskECC()
  101. ;
  102. return $this;
  103. }
  104. /**
  105. * returns a fresh matrix object with the data written for the given $maskPattern
  106. *
  107. * @param int $maskPattern
  108. * @param bool|null $test
  109. *
  110. * @return \chillerlan\QRCode\Data\QRMatrix
  111. */
  112. public function initMatrix(int $maskPattern, bool $test = null):QRMatrix{
  113. $matrix = new QRMatrix($this->version, $this->options->eccLevel);
  114. return $matrix
  115. ->setFinderPattern()
  116. ->setSeparators()
  117. ->setAlignmentPattern()
  118. ->setTimingPattern()
  119. ->setVersionNumber($test)
  120. ->setFormatInfo($maskPattern, $test)
  121. ->setDarkModule()
  122. ->mapData($this->matrixdata, $maskPattern)
  123. ;
  124. }
  125. /**
  126. * returns the length bits for the version breakpoints 1-9, 10-26 and 27-40
  127. *
  128. * @return int
  129. * @throws \chillerlan\QRCode\Data\QRCodeDataException
  130. * @codeCoverageIgnore
  131. */
  132. protected function getLengthBits():int{
  133. foreach([9, 26, 40] as $key => $breakpoint){
  134. if($this->version <= $breakpoint){
  135. return $this->lengthBits[$key];
  136. }
  137. }
  138. throw new QRCodeDataException(sprintf('invalid version number: %d', $this->version));
  139. }
  140. /**
  141. * returns the byte count of the $data string
  142. *
  143. * @param string $data
  144. *
  145. * @return int
  146. */
  147. protected function getLength(string $data):int{
  148. return strlen($data);
  149. }
  150. /**
  151. * returns the minimum version number for the given string
  152. *
  153. * @return int
  154. * @throws \chillerlan\QRCode\Data\QRCodeDataException
  155. */
  156. protected function getMinimumVersion():int{
  157. $maxlength = 0;
  158. // guess the version number within the given range
  159. foreach(range($this->options->versionMin, $this->options->versionMax) as $version){
  160. $maxlength = $this::MAX_LENGTH[$version][QRCode::DATA_MODES[$this->datamode]][QRCode::ECC_MODES[$this->options->eccLevel]];
  161. if($this->strlen <= $maxlength){
  162. return $version;
  163. }
  164. }
  165. throw new QRCodeDataException(sprintf('data exceeds %d characters', $maxlength));
  166. }
  167. /**
  168. * @see \chillerlan\QRCode\Data\QRDataAbstract::writeBitBuffer()
  169. *
  170. * @param string $data
  171. *
  172. * @return void
  173. */
  174. abstract protected function write(string $data):void;
  175. /**
  176. * writes the string data to the BitBuffer
  177. *
  178. * @param string $data
  179. *
  180. * @return \chillerlan\QRCode\Data\QRDataAbstract
  181. * @throws \chillerlan\QRCode\QRCodeException
  182. */
  183. protected function writeBitBuffer(string $data):QRDataInterface{
  184. $this->bitBuffer = new BitBuffer;
  185. $MAX_BITS = $this::MAX_BITS[$this->version][QRCode::ECC_MODES[$this->options->eccLevel]];
  186. $this->bitBuffer
  187. ->clear()
  188. ->put($this->datamode, 4)
  189. ->put($this->strlen, $this->getLengthBits())
  190. ;
  191. $this->write($data);
  192. // there was an error writing the BitBuffer data, which is... unlikely.
  193. if($this->bitBuffer->length > $MAX_BITS){
  194. throw new QRCodeException(sprintf('code length overflow. (%d > %d bit)', $this->bitBuffer->length, $MAX_BITS)); // @codeCoverageIgnore
  195. }
  196. // end code.
  197. if($this->bitBuffer->length + 4 <= $MAX_BITS){
  198. $this->bitBuffer->put(0, 4);
  199. }
  200. // padding
  201. while($this->bitBuffer->length % 8 !== 0){
  202. $this->bitBuffer->putBit(false);
  203. }
  204. // padding
  205. while(true){
  206. if($this->bitBuffer->length >= $MAX_BITS){
  207. break;
  208. }
  209. $this->bitBuffer->put(0xEC, 8);
  210. if($this->bitBuffer->length >= $MAX_BITS){
  211. break;
  212. }
  213. $this->bitBuffer->put(0x11, 8);
  214. }
  215. return $this;
  216. }
  217. /**
  218. * ECC masking
  219. *
  220. * @see \chillerlan\QRCode\Data\QRDataAbstract::writeBitBuffer()
  221. *
  222. * @link http://www.thonky.com/qr-code-tutorial/error-correction-coding
  223. *
  224. * @return array
  225. */
  226. protected function maskECC():array{
  227. [$l1, $l2, $b1, $b2] = $this::RSBLOCKS[$this->version][QRCode::ECC_MODES[$this->options->eccLevel]];
  228. $rsBlocks = array_fill(0, $l1, [$b1, $b2]);
  229. $rsCount = $l1 + $l2;
  230. $this->ecdata = array_fill(0, $rsCount, null);
  231. $this->dcdata = $this->ecdata;
  232. if($l2 > 0){
  233. $rsBlocks = array_merge($rsBlocks, array_fill(0, $l2, [$b1 + 1, $b2 + 1]));
  234. }
  235. $totalCodeCount = 0;
  236. $maxDcCount = 0;
  237. $maxEcCount = 0;
  238. $offset = 0;
  239. foreach($rsBlocks as $key => $block){
  240. [$rsBlockTotal, $dcCount] = $block;
  241. $ecCount = $rsBlockTotal - $dcCount;
  242. $maxDcCount = max($maxDcCount, $dcCount);
  243. $maxEcCount = max($maxEcCount, $ecCount);
  244. $this->dcdata[$key] = array_fill(0, $dcCount, null);
  245. foreach($this->dcdata[$key] as $a => $_z){
  246. $this->dcdata[$key][$a] = 0xff & $this->bitBuffer->buffer[$a + $offset];
  247. }
  248. [$num, $add] = $this->poly($key, $ecCount);
  249. foreach($this->ecdata[$key] as $c => $_z){
  250. $modIndex = $c + $add;
  251. $this->ecdata[$key][$c] = $modIndex >= 0 ? $num[$modIndex] : 0;
  252. }
  253. $offset += $dcCount;
  254. $totalCodeCount += $rsBlockTotal;
  255. }
  256. $data = array_fill(0, $totalCodeCount, null);
  257. $index = 0;
  258. $mask = function($arr, $count) use (&$data, &$index, $rsCount){
  259. for($x = 0; $x < $count; $x++){
  260. for($y = 0; $y < $rsCount; $y++){
  261. if($x < count($arr[$y])){
  262. $data[$index] = $arr[$y][$x];
  263. $index++;
  264. }
  265. }
  266. }
  267. };
  268. $mask($this->dcdata, $maxDcCount);
  269. $mask($this->ecdata, $maxEcCount);
  270. return $data;
  271. }
  272. /**
  273. * @param int $key
  274. * @param int $count
  275. *
  276. * @return int[]
  277. */
  278. protected function poly(int $key, int $count):array{
  279. $rsPoly = new Polynomial;
  280. $modPoly = new Polynomial;
  281. for($i = 0; $i < $count; $i++){
  282. $modPoly->setNum([1, $modPoly->gexp($i)]);
  283. $rsPoly->multiply($modPoly->getNum());
  284. }
  285. $rsPolyCount = count($rsPoly->getNum());
  286. $modPoly
  287. ->setNum($this->dcdata[$key], $rsPolyCount - 1)
  288. ->mod($rsPoly->getNum())
  289. ;
  290. $this->ecdata[$key] = array_fill(0, $rsPolyCount - 1, null);
  291. $num = $modPoly->getNum();
  292. return [
  293. $num,
  294. count($num) - count($this->ecdata[$key]),
  295. ];
  296. }
  297. }