1*fb347f35SAndreas Gohr<?php 2*fb347f35SAndreas Gohr 3*fb347f35SAndreas Gohrnamespace Mpdf\QrCode; 4*fb347f35SAndreas Gohr 5*fb347f35SAndreas Gohr/** 6*fb347f35SAndreas Gohr * QR Code generator 7*fb347f35SAndreas Gohr * 8*fb347f35SAndreas Gohr * @license LGPL 9*fb347f35SAndreas Gohr */ 10*fb347f35SAndreas Gohrclass QrCode 11*fb347f35SAndreas Gohr{ 12*fb347f35SAndreas Gohr 13*fb347f35SAndreas Gohr /** 14*fb347f35SAndreas Gohr * Maximal allowed QR code version 15*fb347f35SAndreas Gohr * 16*fb347f35SAndreas Gohr * @var int 17*fb347f35SAndreas Gohr */ 18*fb347f35SAndreas Gohr private $maxVersion = 40; 19*fb347f35SAndreas Gohr 20*fb347f35SAndreas Gohr /** 21*fb347f35SAndreas Gohr * ECC level 22*fb347f35SAndreas Gohr * 23*fb347f35SAndreas Gohr * @var string 24*fb347f35SAndreas Gohr */ 25*fb347f35SAndreas Gohr private $level; 26*fb347f35SAndreas Gohr 27*fb347f35SAndreas Gohr /** 28*fb347f35SAndreas Gohr * QR code contents 29*fb347f35SAndreas Gohr * @var string 30*fb347f35SAndreas Gohr */ 31*fb347f35SAndreas Gohr private $value; 32*fb347f35SAndreas Gohr 33*fb347f35SAndreas Gohr /** 34*fb347f35SAndreas Gohr * @var int 35*fb347f35SAndreas Gohr */ 36*fb347f35SAndreas Gohr private $length; 37*fb347f35SAndreas Gohr 38*fb347f35SAndreas Gohr /** 39*fb347f35SAndreas Gohr * @var int 40*fb347f35SAndreas Gohr */ 41*fb347f35SAndreas Gohr private $version = 0; 42*fb347f35SAndreas Gohr 43*fb347f35SAndreas Gohr /** 44*fb347f35SAndreas Gohr * Zone data size 45*fb347f35SAndreas Gohr * 46*fb347f35SAndreas Gohr * @var int 47*fb347f35SAndreas Gohr */ 48*fb347f35SAndreas Gohr private $size = 0; 49*fb347f35SAndreas Gohr 50*fb347f35SAndreas Gohr /** 51*fb347f35SAndreas Gohr * QR code dimensions 52*fb347f35SAndreas Gohr * 53*fb347f35SAndreas Gohr * @var int 54*fb347f35SAndreas Gohr */ 55*fb347f35SAndreas Gohr private $qrSize = 0; 56*fb347f35SAndreas Gohr 57*fb347f35SAndreas Gohr /** 58*fb347f35SAndreas Gohr * @var int[] 59*fb347f35SAndreas Gohr */ 60*fb347f35SAndreas Gohr private $bitData; // nb de bit de chacune des valeurs 61*fb347f35SAndreas Gohr 62*fb347f35SAndreas Gohr /** 63*fb347f35SAndreas Gohr * @var int[] 64*fb347f35SAndreas Gohr */ 65*fb347f35SAndreas Gohr private $valData; // liste des valeurs de bit différents 66*fb347f35SAndreas Gohr 67*fb347f35SAndreas Gohr /** 68*fb347f35SAndreas Gohr * @var int[] 69*fb347f35SAndreas Gohr */ 70*fb347f35SAndreas Gohr private $wordData = []; // liste des valeurs tout ramené à 8bit 71*fb347f35SAndreas Gohr 72*fb347f35SAndreas Gohr /** 73*fb347f35SAndreas Gohr * @var int Current position 74*fb347f35SAndreas Gohr */ 75*fb347f35SAndreas Gohr private $ptr; 76*fb347f35SAndreas Gohr 77*fb347f35SAndreas Gohr /** 78*fb347f35SAndreas Gohr * @var int 79*fb347f35SAndreas Gohr */ 80*fb347f35SAndreas Gohr private $dataPtr = 0; 81*fb347f35SAndreas Gohr 82*fb347f35SAndreas Gohr /** 83*fb347f35SAndreas Gohr * @var int 84*fb347f35SAndreas Gohr */ 85*fb347f35SAndreas Gohr private $bitCount; 86*fb347f35SAndreas Gohr 87*fb347f35SAndreas Gohr /** 88*fb347f35SAndreas Gohr * @var int 89*fb347f35SAndreas Gohr */ 90*fb347f35SAndreas Gohr private $dataBitLimit = 0; 91*fb347f35SAndreas Gohr 92*fb347f35SAndreas Gohr /** 93*fb347f35SAndreas Gohr * @var int 94*fb347f35SAndreas Gohr */ 95*fb347f35SAndreas Gohr private $dataWordLimit = 0; 96*fb347f35SAndreas Gohr 97*fb347f35SAndreas Gohr /** 98*fb347f35SAndreas Gohr * @var int 99*fb347f35SAndreas Gohr */ 100*fb347f35SAndreas Gohr private $totalWordLimit = 0; 101*fb347f35SAndreas Gohr 102*fb347f35SAndreas Gohr /** 103*fb347f35SAndreas Gohr * @var int 104*fb347f35SAndreas Gohr */ 105*fb347f35SAndreas Gohr private $ec = 0; 106*fb347f35SAndreas Gohr 107*fb347f35SAndreas Gohr /** 108*fb347f35SAndreas Gohr * @var int[] 109*fb347f35SAndreas Gohr */ 110*fb347f35SAndreas Gohr private $matrix = []; 111*fb347f35SAndreas Gohr 112*fb347f35SAndreas Gohr /** 113*fb347f35SAndreas Gohr * @var int 114*fb347f35SAndreas Gohr */ 115*fb347f35SAndreas Gohr private $matrixRemain = 0; 116*fb347f35SAndreas Gohr 117*fb347f35SAndreas Gohr /** 118*fb347f35SAndreas Gohr * @var int[] 119*fb347f35SAndreas Gohr */ 120*fb347f35SAndreas Gohr private $matrixXArray = []; 121*fb347f35SAndreas Gohr 122*fb347f35SAndreas Gohr /** 123*fb347f35SAndreas Gohr * @var int[] 124*fb347f35SAndreas Gohr */ 125*fb347f35SAndreas Gohr private $matrixYArray = []; 126*fb347f35SAndreas Gohr 127*fb347f35SAndreas Gohr /** 128*fb347f35SAndreas Gohr * @var int[] 129*fb347f35SAndreas Gohr */ 130*fb347f35SAndreas Gohr private $maskArray = []; 131*fb347f35SAndreas Gohr 132*fb347f35SAndreas Gohr /** 133*fb347f35SAndreas Gohr * @var int[] 134*fb347f35SAndreas Gohr */ 135*fb347f35SAndreas Gohr private $formatInformationX1 = []; 136*fb347f35SAndreas Gohr 137*fb347f35SAndreas Gohr /** 138*fb347f35SAndreas Gohr * @var int[] 139*fb347f35SAndreas Gohr */ 140*fb347f35SAndreas Gohr private $formatInformationY1 = []; 141*fb347f35SAndreas Gohr 142*fb347f35SAndreas Gohr /** 143*fb347f35SAndreas Gohr * @var int[] 144*fb347f35SAndreas Gohr */ 145*fb347f35SAndreas Gohr private $formatInformationX2 = []; 146*fb347f35SAndreas Gohr 147*fb347f35SAndreas Gohr /** 148*fb347f35SAndreas Gohr * @var int[] 149*fb347f35SAndreas Gohr */ 150*fb347f35SAndreas Gohr private $formatInformationY2 = []; 151*fb347f35SAndreas Gohr 152*fb347f35SAndreas Gohr /** 153*fb347f35SAndreas Gohr * @var int[] 154*fb347f35SAndreas Gohr */ 155*fb347f35SAndreas Gohr private $rsBlockOrder = []; 156*fb347f35SAndreas Gohr 157*fb347f35SAndreas Gohr /** 158*fb347f35SAndreas Gohr * @var int 159*fb347f35SAndreas Gohr */ 160*fb347f35SAndreas Gohr private $rsEccCodewords = 0; 161*fb347f35SAndreas Gohr 162*fb347f35SAndreas Gohr /** 163*fb347f35SAndreas Gohr * @var int 164*fb347f35SAndreas Gohr */ 165*fb347f35SAndreas Gohr private $byteCount = 0; 166*fb347f35SAndreas Gohr 167*fb347f35SAndreas Gohr /** 168*fb347f35SAndreas Gohr * @var int[] 169*fb347f35SAndreas Gohr */ 170*fb347f35SAndreas Gohr private $final = []; 171*fb347f35SAndreas Gohr 172*fb347f35SAndreas Gohr /** 173*fb347f35SAndreas Gohr * @var bool 174*fb347f35SAndreas Gohr */ 175*fb347f35SAndreas Gohr private $disableBorder = false; 176*fb347f35SAndreas Gohr 177*fb347f35SAndreas Gohr /** 178*fb347f35SAndreas Gohr * @var string 179*fb347f35SAndreas Gohr */ 180*fb347f35SAndreas Gohr const ERROR_CORRECTION_LOW = 'L'; 181*fb347f35SAndreas Gohr 182*fb347f35SAndreas Gohr /** 183*fb347f35SAndreas Gohr * @var string 184*fb347f35SAndreas Gohr */ 185*fb347f35SAndreas Gohr const ERROR_CORRECTION_MEDIUM = 'M'; 186*fb347f35SAndreas Gohr 187*fb347f35SAndreas Gohr /** 188*fb347f35SAndreas Gohr * @var string 189*fb347f35SAndreas Gohr */ 190*fb347f35SAndreas Gohr const ERROR_CORRECTION_QUARTILE = 'Q'; 191*fb347f35SAndreas Gohr 192*fb347f35SAndreas Gohr /** 193*fb347f35SAndreas Gohr * @var string 194*fb347f35SAndreas Gohr */ 195*fb347f35SAndreas Gohr const ERROR_CORRECTION_HIGH = 'H'; 196*fb347f35SAndreas Gohr 197*fb347f35SAndreas Gohr /** 198*fb347f35SAndreas Gohr * @param string $value Contents of the QR code 199*fb347f35SAndreas Gohr * @param string $level Level of error correction (ECC) : L, M, Q, H 200*fb347f35SAndreas Gohr */ 201*fb347f35SAndreas Gohr public function __construct($value, $level = 'L') 202*fb347f35SAndreas Gohr { 203*fb347f35SAndreas Gohr if (!$this->isAllowedErrorCorrectionLevel($level)) { 204*fb347f35SAndreas Gohr throw new \Mpdf\QrCode\QrCodeException('ECC not recognized; valid values are L, M, Q and H'); 205*fb347f35SAndreas Gohr } 206*fb347f35SAndreas Gohr 207*fb347f35SAndreas Gohr $this->length = strlen($value); 208*fb347f35SAndreas Gohr if (!$this->length) { 209*fb347f35SAndreas Gohr throw new \Mpdf\QrCode\QrCodeException('No data for QrCode'); 210*fb347f35SAndreas Gohr } 211*fb347f35SAndreas Gohr 212*fb347f35SAndreas Gohr $this->level = $level; 213*fb347f35SAndreas Gohr $this->value = $value; 214*fb347f35SAndreas Gohr 215*fb347f35SAndreas Gohr $this->bitData = []; 216*fb347f35SAndreas Gohr $this->valData = []; 217*fb347f35SAndreas Gohr $this->ptr = 0; 218*fb347f35SAndreas Gohr $this->bitCount = 0; 219*fb347f35SAndreas Gohr 220*fb347f35SAndreas Gohr $this->encode(); 221*fb347f35SAndreas Gohr $this->loadEcc(); 222*fb347f35SAndreas Gohr $this->makeECC(); 223*fb347f35SAndreas Gohr $this->makeMatrix(); 224*fb347f35SAndreas Gohr } 225*fb347f35SAndreas Gohr 226*fb347f35SAndreas Gohr /** 227*fb347f35SAndreas Gohr * @return int QR code dimensions concerning disabled border 228*fb347f35SAndreas Gohr */ 229*fb347f35SAndreas Gohr public function getQrDimensions() 230*fb347f35SAndreas Gohr { 231*fb347f35SAndreas Gohr if ($this->disableBorder) { 232*fb347f35SAndreas Gohr return $this->qrSize - 8; 233*fb347f35SAndreas Gohr } 234*fb347f35SAndreas Gohr 235*fb347f35SAndreas Gohr return $this->qrSize; 236*fb347f35SAndreas Gohr } 237*fb347f35SAndreas Gohr 238*fb347f35SAndreas Gohr /** 239*fb347f35SAndreas Gohr * @return int QR code dimensions 240*fb347f35SAndreas Gohr */ 241*fb347f35SAndreas Gohr public function getQrSize() 242*fb347f35SAndreas Gohr { 243*fb347f35SAndreas Gohr return $this->qrSize; 244*fb347f35SAndreas Gohr } 245*fb347f35SAndreas Gohr 246*fb347f35SAndreas Gohr public function disableBorder() 247*fb347f35SAndreas Gohr { 248*fb347f35SAndreas Gohr $this->disableBorder = true; 249*fb347f35SAndreas Gohr } 250*fb347f35SAndreas Gohr 251*fb347f35SAndreas Gohr /** 252*fb347f35SAndreas Gohr * @return bool 253*fb347f35SAndreas Gohr */ 254*fb347f35SAndreas Gohr public function isBorderDisabled() 255*fb347f35SAndreas Gohr { 256*fb347f35SAndreas Gohr return $this->disableBorder; 257*fb347f35SAndreas Gohr } 258*fb347f35SAndreas Gohr 259*fb347f35SAndreas Gohr /** 260*fb347f35SAndreas Gohr * @return mixed[] 261*fb347f35SAndreas Gohr */ 262*fb347f35SAndreas Gohr public function getFinal() 263*fb347f35SAndreas Gohr { 264*fb347f35SAndreas Gohr return $this->final; 265*fb347f35SAndreas Gohr } 266*fb347f35SAndreas Gohr 267*fb347f35SAndreas Gohr private function addData($val, $bit, $next = true) 268*fb347f35SAndreas Gohr { 269*fb347f35SAndreas Gohr $this->valData[$this->ptr] = $val; 270*fb347f35SAndreas Gohr $this->bitData[$this->ptr] = $bit; 271*fb347f35SAndreas Gohr 272*fb347f35SAndreas Gohr if ($next) { 273*fb347f35SAndreas Gohr $this->ptr++; 274*fb347f35SAndreas Gohr return $this->ptr - 1; 275*fb347f35SAndreas Gohr } 276*fb347f35SAndreas Gohr 277*fb347f35SAndreas Gohr return $this->ptr; 278*fb347f35SAndreas Gohr } 279*fb347f35SAndreas Gohr 280*fb347f35SAndreas Gohr private function encode() 281*fb347f35SAndreas Gohr { 282*fb347f35SAndreas Gohr // conversion des datas 283*fb347f35SAndreas Gohr if (preg_match('/\D/', $this->value)) { 284*fb347f35SAndreas Gohr if (preg_match('/[^0-9A-Z \$\*\%\+\-\.\/\:]/', $this->value)) { 285*fb347f35SAndreas Gohr 286*fb347f35SAndreas Gohr $this->addData(4, 4); 287*fb347f35SAndreas Gohr 288*fb347f35SAndreas Gohr $this->dataPtr = $this->addData($this->length, 8); /* #version 1-9 */ 289*fb347f35SAndreas Gohr $dataNumCorrection = [ 290*fb347f35SAndreas Gohr 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 291*fb347f35SAndreas Gohr 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 292*fb347f35SAndreas Gohr ]; 293*fb347f35SAndreas Gohr 294*fb347f35SAndreas Gohr // data 295*fb347f35SAndreas Gohr for ($i = 0; $i < $this->length; $i++) { 296*fb347f35SAndreas Gohr $this->addData(ord(substr($this->value, $i, 1)), 8); 297*fb347f35SAndreas Gohr } 298*fb347f35SAndreas Gohr 299*fb347f35SAndreas Gohr } else { 300*fb347f35SAndreas Gohr 301*fb347f35SAndreas Gohr $this->addData(2, 4); 302*fb347f35SAndreas Gohr 303*fb347f35SAndreas Gohr $this->dataPtr = $this->addData($this->length, 9); /* #version 1-9 */ 304*fb347f35SAndreas Gohr $dataNumCorrection = [ 305*fb347f35SAndreas Gohr 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 306*fb347f35SAndreas Gohr 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 307*fb347f35SAndreas Gohr ]; 308*fb347f35SAndreas Gohr 309*fb347f35SAndreas Gohr $alNumHash = [ 310*fb347f35SAndreas Gohr '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, 311*fb347f35SAndreas Gohr 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14, 'F' => 15, 'G' => 16, 'H' => 17, 'I' => 18, 312*fb347f35SAndreas Gohr 'J' => 19, 'K' => 20, 'L' => 21, 'M' => 22, 'N' => 23, 'O' => 24, 'P' => 25, 'Q' => 26, 'R' => 27, 313*fb347f35SAndreas Gohr 'S' => 28, 'T' => 29, 'U' => 30, 'V' => 31, 'W' => 32, 'X' => 33, 'Y' => 34, 'Z' => 35, ' ' => 36, 314*fb347f35SAndreas Gohr '$' => 37, '%' => 38, '*' => 39, '+' => 40, '-' => 41, '.' => 42, '/' => 43, ':' => 44 315*fb347f35SAndreas Gohr ]; 316*fb347f35SAndreas Gohr 317*fb347f35SAndreas Gohr for ($i = 0; $i < $this->length; $i++) { 318*fb347f35SAndreas Gohr if (($i % 2) === 0) { 319*fb347f35SAndreas Gohr $this->addData($alNumHash[$this->value[$i]], 6, false); 320*fb347f35SAndreas Gohr } else { 321*fb347f35SAndreas Gohr $this->addData($this->valData[$this->ptr] * 45 + $alNumHash[$this->value[$i]], 11); 322*fb347f35SAndreas Gohr } 323*fb347f35SAndreas Gohr } 324*fb347f35SAndreas Gohr 325*fb347f35SAndreas Gohr unset($alNumHash); 326*fb347f35SAndreas Gohr 327*fb347f35SAndreas Gohr if (isset($this->bitData[$this->ptr])) { 328*fb347f35SAndreas Gohr $this->ptr++; 329*fb347f35SAndreas Gohr } 330*fb347f35SAndreas Gohr } 331*fb347f35SAndreas Gohr 332*fb347f35SAndreas Gohr } else { 333*fb347f35SAndreas Gohr 334*fb347f35SAndreas Gohr $this->addData(1, 4); 335*fb347f35SAndreas Gohr 336*fb347f35SAndreas Gohr $this->dataPtr = $this->addData($this->length, 10); /* #version 1-9 */ 337*fb347f35SAndreas Gohr $dataNumCorrection = [ 338*fb347f35SAndreas Gohr 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 339*fb347f35SAndreas Gohr 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 340*fb347f35SAndreas Gohr ]; 341*fb347f35SAndreas Gohr 342*fb347f35SAndreas Gohr // data 343*fb347f35SAndreas Gohr for ($i = 0; $i < $this->length; $i++) { 344*fb347f35SAndreas Gohr if (($i % 3) === 0) { 345*fb347f35SAndreas Gohr $this->addData((int) $this->value[$i], 4, false); 346*fb347f35SAndreas Gohr } elseif (($i % 3) === 1) { 347*fb347f35SAndreas Gohr $this->addData($this->valData[$this->ptr] * 10 + (int) $this->value[$i], 7, false); 348*fb347f35SAndreas Gohr } else { 349*fb347f35SAndreas Gohr $this->addData($this->valData[$this->ptr] * 10 + (int) $this->value[$i], 10); 350*fb347f35SAndreas Gohr } 351*fb347f35SAndreas Gohr } 352*fb347f35SAndreas Gohr 353*fb347f35SAndreas Gohr if (isset($this->bitData[$this->ptr])) { 354*fb347f35SAndreas Gohr $this->ptr++; 355*fb347f35SAndreas Gohr } 356*fb347f35SAndreas Gohr } 357*fb347f35SAndreas Gohr 358*fb347f35SAndreas Gohr // calculate bit count 359*fb347f35SAndreas Gohr $this->bitCount = 0; 360*fb347f35SAndreas Gohr foreach ($this->bitData as $bit) { 361*fb347f35SAndreas Gohr $this->bitCount += $bit; 362*fb347f35SAndreas Gohr } 363*fb347f35SAndreas Gohr 364*fb347f35SAndreas Gohr // code ECC 365*fb347f35SAndreas Gohr $ecHash = [ 366*fb347f35SAndreas Gohr static::ERROR_CORRECTION_LOW => 1, 367*fb347f35SAndreas Gohr static::ERROR_CORRECTION_MEDIUM => 0, 368*fb347f35SAndreas Gohr static::ERROR_CORRECTION_QUARTILE => 3, 369*fb347f35SAndreas Gohr static::ERROR_CORRECTION_HIGH => 2 370*fb347f35SAndreas Gohr ]; 371*fb347f35SAndreas Gohr 372*fb347f35SAndreas Gohr $this->ec = $ecHash[$this->level]; 373*fb347f35SAndreas Gohr 374*fb347f35SAndreas Gohr // bit size limit array 375*fb347f35SAndreas Gohr $maxBits = [ 376*fb347f35SAndreas Gohr 0, 128, 224, 352, 512, 688, 864, 992, 1232, 1456, 1728, 2032, 2320, 2672, 2920, 3320, 3624, 4056, 4504, 5016, 5352, 377*fb347f35SAndreas Gohr 5712, 6256, 6880, 7312, 8000, 8496, 9024, 9544, 10136, 10984, 11640, 12328, 13048, 13800, 14496, 15312, 15936, 16816, 17728, 18672, 378*fb347f35SAndreas Gohr 379*fb347f35SAndreas Gohr 152, 272, 440, 640, 864, 1088, 1248, 1552, 1856, 2192, 2592, 2960, 3424, 3688, 4184, 4712, 5176, 5768, 6360, 6888, 380*fb347f35SAndreas Gohr 7456, 8048, 8752, 9392, 10208, 10960, 11744, 12248, 13048, 13880, 14744, 15640, 16568, 17528, 18448, 19472, 20528, 21616, 22496, 23648, 381*fb347f35SAndreas Gohr 382*fb347f35SAndreas Gohr 72, 128, 208, 288, 368, 480, 528, 688, 800, 976, 1120, 1264, 1440, 1576, 1784, 2024, 2264, 2504, 2728, 3080, 383*fb347f35SAndreas Gohr 3248, 3536, 3712, 4112, 4304, 4768, 5024, 5288, 5608, 5960, 6344, 6760, 7208, 7688, 7888, 8432, 8768, 9136, 9776, 10208, 384*fb347f35SAndreas Gohr 385*fb347f35SAndreas Gohr 104, 176, 272, 384, 496, 608, 704, 880, 1056, 1232, 1440, 1648, 1952, 2088, 2360, 2600, 2936, 3176, 3560, 3880, 386*fb347f35SAndreas Gohr 4096, 4544, 4912, 5312, 5744, 6032, 6464, 6968, 7288, 7880, 8264, 8920, 9368, 9848, 10288, 10832, 11408, 12016, 12656, 13328, 387*fb347f35SAndreas Gohr ]; 388*fb347f35SAndreas Gohr 389*fb347f35SAndreas Gohr // version determination 390*fb347f35SAndreas Gohr $this->version = 1; 391*fb347f35SAndreas Gohr $i = 1 + 40 * $this->ec; 392*fb347f35SAndreas Gohr $j = $i + 39; 393*fb347f35SAndreas Gohr while ($i <= $j) { 394*fb347f35SAndreas Gohr if ($maxBits[$i] >= $this->bitCount + $dataNumCorrection[$this->version]) { 395*fb347f35SAndreas Gohr $this->dataBitLimit = $maxBits[$i]; 396*fb347f35SAndreas Gohr break; 397*fb347f35SAndreas Gohr } 398*fb347f35SAndreas Gohr $i++; 399*fb347f35SAndreas Gohr $this->version++; 400*fb347f35SAndreas Gohr } 401*fb347f35SAndreas Gohr 402*fb347f35SAndreas Gohr if ($this->version > $this->maxVersion) { 403*fb347f35SAndreas Gohr throw new \Mpdf\QrCode\QrCodeException('QrCode version too large'); 404*fb347f35SAndreas Gohr } 405*fb347f35SAndreas Gohr 406*fb347f35SAndreas Gohr // strlen bits of the value number fix 407*fb347f35SAndreas Gohr $this->bitCount += $dataNumCorrection[$this->version]; 408*fb347f35SAndreas Gohr $this->bitData[$this->dataPtr] += $dataNumCorrection[$this->version]; 409*fb347f35SAndreas Gohr $this->dataWordLimit = ($this->dataBitLimit / 8); 410*fb347f35SAndreas Gohr 411*fb347f35SAndreas Gohr // maximal word counts 412*fb347f35SAndreas Gohr $maxWordCountArray = [ 413*fb347f35SAndreas Gohr 0, 26, 44, 70, 100, 134, 172, 196, 242, 292, 346, 404, 466, 532, 581, 655, 733, 815, 901, 991, 1085, 1156, 414*fb347f35SAndreas Gohr 1258, 1364, 1474, 1588, 1706, 1828, 1921, 2051, 2185, 2323, 2465, 2611, 2761, 2876, 3034, 3196, 3362, 3532, 3706 415*fb347f35SAndreas Gohr ]; 416*fb347f35SAndreas Gohr 417*fb347f35SAndreas Gohr $this->totalWordLimit = $maxWordCountArray[$this->version]; 418*fb347f35SAndreas Gohr $this->size = 17 + 4 * $this->version; 419*fb347f35SAndreas Gohr 420*fb347f35SAndreas Gohr unset($maxBits, $dataNumCorrection, $maxWordCountArray, $ecHash); 421*fb347f35SAndreas Gohr 422*fb347f35SAndreas Gohr // terminator 423*fb347f35SAndreas Gohr if ($this->bitCount <= $this->dataBitLimit - 4) { 424*fb347f35SAndreas Gohr $this->addData(0, 4); 425*fb347f35SAndreas Gohr } elseif ($this->bitCount < $this->dataBitLimit) { 426*fb347f35SAndreas Gohr $this->addData(0, $this->dataBitLimit - $this->bitCount); 427*fb347f35SAndreas Gohr } elseif ($this->bitCount > $this->dataBitLimit) { 428*fb347f35SAndreas Gohr throw new \Mpdf\QrCode\QrCodeException('QrCode data overflow error'); 429*fb347f35SAndreas Gohr } 430*fb347f35SAndreas Gohr 431*fb347f35SAndreas Gohr // construction of 8bit words 432*fb347f35SAndreas Gohr $this->wordData = []; 433*fb347f35SAndreas Gohr $this->wordData[0] = 0; 434*fb347f35SAndreas Gohr $wordCount = 0; 435*fb347f35SAndreas Gohr 436*fb347f35SAndreas Gohr $remainingBit = 8; 437*fb347f35SAndreas Gohr for ($i = 0; $i < $this->ptr; $i++) { 438*fb347f35SAndreas Gohr $bufferVal = $this->valData[$i]; 439*fb347f35SAndreas Gohr $bufferBit = $this->bitData[$i]; 440*fb347f35SAndreas Gohr 441*fb347f35SAndreas Gohr $flag = true; 442*fb347f35SAndreas Gohr while ($flag) { 443*fb347f35SAndreas Gohr if ($remainingBit > $bufferBit) { 444*fb347f35SAndreas Gohr $this->wordData[$wordCount] = ((@$this->wordData[$wordCount] << $bufferBit) | $bufferVal); 445*fb347f35SAndreas Gohr $remainingBit -= $bufferBit; 446*fb347f35SAndreas Gohr $flag = false; 447*fb347f35SAndreas Gohr } else { 448*fb347f35SAndreas Gohr $bufferBit -= $remainingBit; 449*fb347f35SAndreas Gohr $this->wordData[$wordCount] = ((@$this->wordData[$wordCount] << $remainingBit) | ($bufferVal >> $bufferBit)); 450*fb347f35SAndreas Gohr $wordCount++; 451*fb347f35SAndreas Gohr 452*fb347f35SAndreas Gohr if ($bufferBit === 0) { 453*fb347f35SAndreas Gohr $flag = false; 454*fb347f35SAndreas Gohr } else { 455*fb347f35SAndreas Gohr $bufferVal &= ((1 << $bufferBit) - 1); 456*fb347f35SAndreas Gohr } 457*fb347f35SAndreas Gohr 458*fb347f35SAndreas Gohr if ($wordCount < $this->dataWordLimit - 1) { 459*fb347f35SAndreas Gohr $this->wordData[$wordCount] = 0; 460*fb347f35SAndreas Gohr } 461*fb347f35SAndreas Gohr $remainingBit = 8; 462*fb347f35SAndreas Gohr } 463*fb347f35SAndreas Gohr } 464*fb347f35SAndreas Gohr } 465*fb347f35SAndreas Gohr 466*fb347f35SAndreas Gohr // completion of the last word if incomplete 467*fb347f35SAndreas Gohr if ($remainingBit < 8) { 468*fb347f35SAndreas Gohr $this->wordData[$wordCount] <<= $remainingBit; 469*fb347f35SAndreas Gohr } else { 470*fb347f35SAndreas Gohr $wordCount--; 471*fb347f35SAndreas Gohr } 472*fb347f35SAndreas Gohr 473*fb347f35SAndreas Gohr // fill the rest 474*fb347f35SAndreas Gohr if ($wordCount < $this->dataWordLimit - 1) { 475*fb347f35SAndreas Gohr $flag = true; 476*fb347f35SAndreas Gohr while ($wordCount < $this->dataWordLimit - 1) { 477*fb347f35SAndreas Gohr $wordCount++; 478*fb347f35SAndreas Gohr if ($flag) { 479*fb347f35SAndreas Gohr $this->wordData[$wordCount] = 236; 480*fb347f35SAndreas Gohr } else { 481*fb347f35SAndreas Gohr $this->wordData[$wordCount] = 17; 482*fb347f35SAndreas Gohr } 483*fb347f35SAndreas Gohr $flag = !$flag; 484*fb347f35SAndreas Gohr } 485*fb347f35SAndreas Gohr } 486*fb347f35SAndreas Gohr } 487*fb347f35SAndreas Gohr 488*fb347f35SAndreas Gohr private function loadEcc() 489*fb347f35SAndreas Gohr { 490*fb347f35SAndreas Gohr $matrixRemainBits = [0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0]; 491*fb347f35SAndreas Gohr 492*fb347f35SAndreas Gohr $this->matrixRemain = $matrixRemainBits[$this->version]; 493*fb347f35SAndreas Gohr unset($matrixRemainBits); 494*fb347f35SAndreas Gohr 495*fb347f35SAndreas Gohr // data file of geometry & mask for version V, ecc level N 496*fb347f35SAndreas Gohr $this->byteCount = $this->matrixRemain + 8 * $this->totalWordLimit; 497*fb347f35SAndreas Gohr 498*fb347f35SAndreas Gohr $filename = __DIR__ . '/../data/qrv' . $this->version . '_' . $this->ec . '.dat'; 499*fb347f35SAndreas Gohr 500*fb347f35SAndreas Gohr $fp1 = fopen($filename, 'rb'); 501*fb347f35SAndreas Gohr 502*fb347f35SAndreas Gohr $this->matrixXArray = unpack('C*', fread($fp1, $this->byteCount)); 503*fb347f35SAndreas Gohr $this->matrixYArray = unpack('C*', fread($fp1, $this->byteCount)); 504*fb347f35SAndreas Gohr $this->maskArray = unpack('C*', fread($fp1, $this->byteCount)); 505*fb347f35SAndreas Gohr $this->formatInformationX2 = unpack('C*', fread($fp1, 15)); 506*fb347f35SAndreas Gohr $this->formatInformationY2 = unpack('C*', fread($fp1, 15)); 507*fb347f35SAndreas Gohr $this->rsEccCodewords = ord(fread($fp1, 1)); 508*fb347f35SAndreas Gohr $this->rsBlockOrder = unpack('C*', fread($fp1, 128)); 509*fb347f35SAndreas Gohr 510*fb347f35SAndreas Gohr fclose($fp1); 511*fb347f35SAndreas Gohr 512*fb347f35SAndreas Gohr $this->formatInformationX1 = [0, 1, 2, 3, 4, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8]; 513*fb347f35SAndreas Gohr $this->formatInformationY1 = [8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 4, 3, 2, 1, 0]; 514*fb347f35SAndreas Gohr } 515*fb347f35SAndreas Gohr 516*fb347f35SAndreas Gohr private function makeECC() 517*fb347f35SAndreas Gohr { 518*fb347f35SAndreas Gohr // calculating tables for RS encoding 519*fb347f35SAndreas Gohr $rsTable = []; 520*fb347f35SAndreas Gohr 521*fb347f35SAndreas Gohr $filename = __DIR__ . '/../data/rsc' . $this->rsEccCodewords . '.dat'; 522*fb347f35SAndreas Gohr 523*fb347f35SAndreas Gohr $fp0 = fopen($filename, 'rb'); 524*fb347f35SAndreas Gohr for ($i = 0; $i < 256; $i++) { 525*fb347f35SAndreas Gohr $rsTable[$i] = fread($fp0, $this->rsEccCodewords); 526*fb347f35SAndreas Gohr } 527*fb347f35SAndreas Gohr fclose($fp0); 528*fb347f35SAndreas Gohr 529*fb347f35SAndreas Gohr // preparation 530*fb347f35SAndreas Gohr $j = 0; 531*fb347f35SAndreas Gohr $rsBlockNumber = 0; 532*fb347f35SAndreas Gohr $rsTemp[0] = ''; 533*fb347f35SAndreas Gohr 534*fb347f35SAndreas Gohr foreach ($this->wordData as $wordItem) { 535*fb347f35SAndreas Gohr $rsTemp[$rsBlockNumber] .= chr($wordItem); 536*fb347f35SAndreas Gohr $j++; 537*fb347f35SAndreas Gohr if ($j >= $this->rsBlockOrder[$rsBlockNumber + 1] - $this->rsEccCodewords) { 538*fb347f35SAndreas Gohr $j = 0; 539*fb347f35SAndreas Gohr $rsBlockNumber++; 540*fb347f35SAndreas Gohr $rsTemp[$rsBlockNumber] = ''; 541*fb347f35SAndreas Gohr } 542*fb347f35SAndreas Gohr } 543*fb347f35SAndreas Gohr 544*fb347f35SAndreas Gohr $rsBlockOrderNum = count($this->rsBlockOrder); 545*fb347f35SAndreas Gohr $data = []; 546*fb347f35SAndreas Gohr 547*fb347f35SAndreas Gohr for ($rsBlockNumber = 0; $rsBlockNumber < $rsBlockOrderNum; $rsBlockNumber++) { 548*fb347f35SAndreas Gohr $rsCodewords = $this->rsBlockOrder[$rsBlockNumber + 1]; 549*fb347f35SAndreas Gohr $rsDataCodewords = $rsCodewords - $this->rsEccCodewords; 550*fb347f35SAndreas Gohr 551*fb347f35SAndreas Gohr $rst = $rsTemp[$rsBlockNumber] . str_repeat(chr(0), $this->rsEccCodewords); 552*fb347f35SAndreas Gohr $paddingData = str_repeat(chr(0), $rsDataCodewords); 553*fb347f35SAndreas Gohr 554*fb347f35SAndreas Gohr $j = $rsDataCodewords; 555*fb347f35SAndreas Gohr while ($j > 0) { 556*fb347f35SAndreas Gohr $first = ord($rst[0]); 557*fb347f35SAndreas Gohr 558*fb347f35SAndreas Gohr if ($first) { 559*fb347f35SAndreas Gohr $leftChr = substr($rst, 1); 560*fb347f35SAndreas Gohr $cal = $rsTable[$first] . $paddingData; 561*fb347f35SAndreas Gohr $rst = $leftChr ^ $cal; 562*fb347f35SAndreas Gohr } else { 563*fb347f35SAndreas Gohr $rst = substr($rst, 1); 564*fb347f35SAndreas Gohr } 565*fb347f35SAndreas Gohr $j--; 566*fb347f35SAndreas Gohr } 567*fb347f35SAndreas Gohr 568*fb347f35SAndreas Gohr $data[] = unpack('C*', $rst); 569*fb347f35SAndreas Gohr } 570*fb347f35SAndreas Gohr 571*fb347f35SAndreas Gohr 572*fb347f35SAndreas Gohr $this->wordData = array_merge($this->wordData, ...$data); 573*fb347f35SAndreas Gohr } 574*fb347f35SAndreas Gohr 575*fb347f35SAndreas Gohr private function makeMatrix() 576*fb347f35SAndreas Gohr { 577*fb347f35SAndreas Gohr // preparation 578*fb347f35SAndreas Gohr $this->matrix = array_fill(0, $this->size, array_fill(0, $this->size, 0)); 579*fb347f35SAndreas Gohr 580*fb347f35SAndreas Gohr // put in words 581*fb347f35SAndreas Gohr for ($i = 0; $i < $this->totalWordLimit; $i++) { 582*fb347f35SAndreas Gohr $word = $this->wordData[$i]; 583*fb347f35SAndreas Gohr for ($j = 8; $j > 0; $j--) { 584*fb347f35SAndreas Gohr $bitPos = ($i << 3) + $j; 585*fb347f35SAndreas Gohr $this->matrix[$this->matrixXArray[$bitPos]][$this->matrixYArray[$bitPos]] = ((255 * ($word & 1)) ^ $this->maskArray[$bitPos]); 586*fb347f35SAndreas Gohr $word >>= 1; 587*fb347f35SAndreas Gohr } 588*fb347f35SAndreas Gohr } 589*fb347f35SAndreas Gohr 590*fb347f35SAndreas Gohr for ($k = $this->matrixRemain; $k > 0; $k--) { 591*fb347f35SAndreas Gohr $bitPos = $k + ($this->totalWordLimit << 3); 592*fb347f35SAndreas Gohr $this->matrix[$this->matrixXArray[$bitPos]][$this->matrixYArray[$bitPos]] = (255 ^ $this->maskArray[$bitPos]); 593*fb347f35SAndreas Gohr } 594*fb347f35SAndreas Gohr 595*fb347f35SAndreas Gohr // mask select 596*fb347f35SAndreas Gohr $minDemeritScore = 0; 597*fb347f35SAndreas Gohr $hMaster = ''; 598*fb347f35SAndreas Gohr $vMaster = ''; 599*fb347f35SAndreas Gohr $k = 0; 600*fb347f35SAndreas Gohr while ($k < $this->size) { 601*fb347f35SAndreas Gohr $l = 0; 602*fb347f35SAndreas Gohr while ($l < $this->size) { 603*fb347f35SAndreas Gohr $hMaster .= chr($this->matrix[$l][$k]); 604*fb347f35SAndreas Gohr $vMaster .= chr($this->matrix[$k][$l]); 605*fb347f35SAndreas Gohr $l++; 606*fb347f35SAndreas Gohr } 607*fb347f35SAndreas Gohr $k++; 608*fb347f35SAndreas Gohr } 609*fb347f35SAndreas Gohr 610*fb347f35SAndreas Gohr $i = 0; 611*fb347f35SAndreas Gohr $allMatrix = $this->size * $this->size; 612*fb347f35SAndreas Gohr 613*fb347f35SAndreas Gohr $maskNumber = 0; 614*fb347f35SAndreas Gohr while ($i < 8) { 615*fb347f35SAndreas Gohr 616*fb347f35SAndreas Gohr $demeritN1 = 0; 617*fb347f35SAndreas Gohr $ptnTemp = []; 618*fb347f35SAndreas Gohr $bit = 1 << $i; 619*fb347f35SAndreas Gohr $bitR = (~$bit) & 255; 620*fb347f35SAndreas Gohr $bitMask = str_repeat(chr($bit), $allMatrix); 621*fb347f35SAndreas Gohr $hor = $hMaster & $bitMask; 622*fb347f35SAndreas Gohr $ver = $vMaster & $bitMask; 623*fb347f35SAndreas Gohr 624*fb347f35SAndreas Gohr $vShift1 = $ver . str_repeat(chr(170), $this->size); 625*fb347f35SAndreas Gohr $vShift2 = str_repeat(chr(170), $this->size) . $ver; 626*fb347f35SAndreas Gohr $vShift10 = $ver . str_repeat(chr(0), $this->size); 627*fb347f35SAndreas Gohr $vShift20 = str_repeat(chr(0), $this->size) . $ver; 628*fb347f35SAndreas Gohr $verOr = chunk_split(~($vShift1 | $vShift2), $this->size, chr(170)); 629*fb347f35SAndreas Gohr $verAnd = chunk_split(~($vShift10 & $vShift20), $this->size, chr(170)); 630*fb347f35SAndreas Gohr 631*fb347f35SAndreas Gohr $hor = chunk_split(~$hor, $this->size, chr(170)); 632*fb347f35SAndreas Gohr $ver = chunk_split(~$ver, $this->size, chr(170)); 633*fb347f35SAndreas Gohr $hor = $hor . chr(170) . $ver; 634*fb347f35SAndreas Gohr 635*fb347f35SAndreas Gohr $n1Search = '/' . str_repeat(chr(255), 5) . '+|' . str_repeat(chr($bitR), 5) . '+/'; 636*fb347f35SAndreas Gohr $n3Search = chr($bitR) . chr(255) . chr($bitR) . chr($bitR) . chr($bitR) . chr(255) . chr($bitR); 637*fb347f35SAndreas Gohr 638*fb347f35SAndreas Gohr $demeritN3 = substr_count($hor, $n3Search) * 40; 639*fb347f35SAndreas Gohr $demeritN4 = floor(abs(((100 * (substr_count($ver, chr($bitR)) / $this->byteCount)) - 50) / 5)) * 10; 640*fb347f35SAndreas Gohr 641*fb347f35SAndreas Gohr $n2Search1 = '/' . chr($bitR) . chr($bitR) . '+/'; 642*fb347f35SAndreas Gohr $n2Search2 = '/' . chr(255) . chr(255) . '+/'; 643*fb347f35SAndreas Gohr $demeritN2 = 0; 644*fb347f35SAndreas Gohr 645*fb347f35SAndreas Gohr preg_match_all($n2Search1, $verAnd, $ptnTemp); 646*fb347f35SAndreas Gohr foreach ($ptnTemp[0] as $strTemp) { 647*fb347f35SAndreas Gohr $demeritN2 += (strlen($strTemp) - 1); 648*fb347f35SAndreas Gohr } 649*fb347f35SAndreas Gohr 650*fb347f35SAndreas Gohr $ptnTemp = []; 651*fb347f35SAndreas Gohr preg_match_all($n2Search2, $verOr, $ptnTemp); 652*fb347f35SAndreas Gohr foreach ($ptnTemp[0] as $strTemp) { 653*fb347f35SAndreas Gohr $demeritN2 += (strlen($strTemp) - 1); 654*fb347f35SAndreas Gohr } 655*fb347f35SAndreas Gohr $demeritN2 *= 3; 656*fb347f35SAndreas Gohr 657*fb347f35SAndreas Gohr $ptnTemp = []; 658*fb347f35SAndreas Gohr 659*fb347f35SAndreas Gohr preg_match_all($n1Search, $hor, $ptnTemp); 660*fb347f35SAndreas Gohr foreach ($ptnTemp[0] as $strTemp) { 661*fb347f35SAndreas Gohr $demeritN1 += (strlen($strTemp) - 2); 662*fb347f35SAndreas Gohr } 663*fb347f35SAndreas Gohr $demeritScore = $demeritN1 + $demeritN2 + $demeritN3 + $demeritN4; 664*fb347f35SAndreas Gohr 665*fb347f35SAndreas Gohr if ($demeritScore <= $minDemeritScore || $i === 0) { 666*fb347f35SAndreas Gohr $maskNumber = $i; 667*fb347f35SAndreas Gohr $minDemeritScore = $demeritScore; 668*fb347f35SAndreas Gohr } 669*fb347f35SAndreas Gohr 670*fb347f35SAndreas Gohr $i++; 671*fb347f35SAndreas Gohr } 672*fb347f35SAndreas Gohr 673*fb347f35SAndreas Gohr $maskContent = 1 << $maskNumber; 674*fb347f35SAndreas Gohr 675*fb347f35SAndreas Gohr $formatInformationValue = (($this->ec << 3) | $maskNumber); 676*fb347f35SAndreas Gohr $formatInformationArray = [ 677*fb347f35SAndreas Gohr '101010000010010', '101000100100101', 678*fb347f35SAndreas Gohr '101111001111100', '101101101001011', '100010111111001', '100000011001110', 679*fb347f35SAndreas Gohr '100111110010111', '100101010100000', '111011111000100', '111001011110011', 680*fb347f35SAndreas Gohr '111110110101010', '111100010011101', '110011000101111', '110001100011000', 681*fb347f35SAndreas Gohr '110110001000001', '110100101110110', '001011010001001', '001001110111110', 682*fb347f35SAndreas Gohr '001110011100111', '001100111010000', '000011101100010', '000001001010101', 683*fb347f35SAndreas Gohr '000110100001100', '000100000111011', '011010101011111', '011000001101000', 684*fb347f35SAndreas Gohr '011111100110001', '011101000000110', '010010010110100', '010000110000011', 685*fb347f35SAndreas Gohr '010111011011010', '010101111101101' 686*fb347f35SAndreas Gohr ]; 687*fb347f35SAndreas Gohr 688*fb347f35SAndreas Gohr for ($i = 0; $i < 15; $i++) { 689*fb347f35SAndreas Gohr $content = (int) $formatInformationArray[$formatInformationValue][$i]; 690*fb347f35SAndreas Gohr 691*fb347f35SAndreas Gohr $this->matrix[$this->formatInformationX1[$i]][$this->formatInformationY1[$i]] = $content * 255; 692*fb347f35SAndreas Gohr $this->matrix[$this->formatInformationX2[$i + 1]][$this->formatInformationY2[$i + 1]] = $content * 255; 693*fb347f35SAndreas Gohr } 694*fb347f35SAndreas Gohr 695*fb347f35SAndreas Gohr $this->final = unpack('C*', file_get_contents(__DIR__ . '/../data/modele' . $this->version . '.dat')); 696*fb347f35SAndreas Gohr $this->qrSize = $this->size + 8; 697*fb347f35SAndreas Gohr 698*fb347f35SAndreas Gohr for ($x = 0; $x < $this->size; $x++) { 699*fb347f35SAndreas Gohr for ($y = 0; $y < $this->size; $y++) { 700*fb347f35SAndreas Gohr if ($this->matrix[$x][$y] & $maskContent) { 701*fb347f35SAndreas Gohr $this->final[($x + 4) + ($y + 4) * $this->qrSize + 1] = true; 702*fb347f35SAndreas Gohr } 703*fb347f35SAndreas Gohr } 704*fb347f35SAndreas Gohr } 705*fb347f35SAndreas Gohr } 706*fb347f35SAndreas Gohr 707*fb347f35SAndreas Gohr private function isAllowedErrorCorrectionLevel($level) 708*fb347f35SAndreas Gohr { 709*fb347f35SAndreas Gohr return \in_array($level, [ 710*fb347f35SAndreas Gohr static::ERROR_CORRECTION_LOW, 711*fb347f35SAndreas Gohr static::ERROR_CORRECTION_MEDIUM, 712*fb347f35SAndreas Gohr static::ERROR_CORRECTION_QUARTILE, 713*fb347f35SAndreas Gohr static::ERROR_CORRECTION_HIGH, 714*fb347f35SAndreas Gohr ], true); 715*fb347f35SAndreas Gohr } 716*fb347f35SAndreas Gohr 717*fb347f35SAndreas Gohr} 718