QRDataAbstract.php 7.9 KB

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