9 * (QR Code is registered trademark of DENSO WAVE INCORPORATED | http://www.denso-wave.com/qrcode/)
10 * Fortement inspiré de "QRcode image PHP scripts version 0.50g (C)2000-2005,Y.Swetake"
12 * Distribué sous la licence LGPL.
14 * @author Laurent MINGUET <webmaster@spipu.net>
19 private $version_mx = 40; // numero de version maximal autorisé
21 private $type = 'bin'; // type de donnée
23 private $level = 'L'; // ECC
25 private $value = ''; // valeur a encoder
27 private $length = 0; // taille de la valeur
29 private $version = 0; // version
31 private $size = 0; // dimension de la zone data
33 private $qr_size = 0; // dimension du QRcode
35 private $data_bit = []; // nb de bit de chacune des valeurs
37 private $data_val = []; // liste des valeurs de bit différents
39 private $data_word = []; // liste des valeurs tout ramené à 8bit
41 private $data_cur = 0; // position courante
43 private $data_num = 0; // position de la dimension
45 private $data_bits = 0; // nom de bit au total
47 private $max_data_bit = 0; // lilmite de nombre de bit maximal pour les datas
49 private $max_data_word = 0; // lilmite de nombre de mot maximal pour les datas
51 private $max_word = 0; // lilmite de nombre de mot maximal en global
57 private $matrix_remain = 0;
59 private $matrix_x_array = [];
61 private $matrix_y_array = [];
63 private $mask_array = [];
65 private $format_information_x1 = [];
67 private $format_information_y1 = [];
69 private $format_information_x2 = [];
71 private $format_information_y2 = [];
73 private $rs_block_order = [];
75 private $rs_ecc_codewords = 0;
77 private $byte_num = 0;
81 private $disable_border = false;
84 * @param string $value message a encoder
85 * @param string $level niveau de correction d'erreur (ECC) : L, M, Q, H
87 public function __construct($value, $level = 'L')
89 if (!in_array($level, ['L', 'M', 'Q', 'H'])) {
90 throw new \Mpdf\QrCode\
QrCodeException('ECC not recognized : L, M, Q, H');
93 $this->length
= strlen($value);
95 throw new \Mpdf\QrCode\
QrCodeException('No data for QrCode');
98 $this->level
= $level;
99 $this->value
= $value;
101 $this->data_bit
= [];
102 $this->data_val
= [];
104 $this->data_bits
= 0;
113 * permet de recuperer la taille du QRcode (le nombre de case de côté)
115 * @return int size of qrcode
117 public function getQrSize()
119 if ($this->disable_border
) {
120 return $this->qr_size
- 8;
122 return $this->qr_size
;
126 public function disableBorder()
128 $this->disable_border
= true;
132 * permet d'afficher le QRcode dans un pdf via FPDF
134 * @param \Mpdf\Mpdf $mpdf objet fpdf
135 * @param float $x position X
136 * @param float $y position Y
137 * @param float $w taille du qrcode
138 * @param array $background couleur du background (R,V,B)
139 * @param array $color couleur des cases et du border (R,V,B)
141 * @return boolean true;
143 public function displayFPDF(Mpdf
$mpdf, $x, $y, $w, $background = [255, 255, 255], $color = [0, 0, 0])
146 $s = $size / $this->getQrSize();
148 $mpdf->SetDrawColor($color[0], $color[1], $color[2]);
149 $mpdf->SetFillColor($background[0], $background[1], $background[2]);
152 if ($this->disable_border
) {
154 $s_max = $this->qr_size
- 4;
155 $mpdf->Rect($x, $y, $size, $size, 'F');
158 $s_max = $this->qr_size
;
159 $mpdf->Rect($x, $y, $size, $size, 'FD');
162 $mpdf->SetFillColor($color[0], $color[1], $color[2]);
163 for ($j = $s_min; $j < $s_max; $j++
) {
164 for ($i = $s_min; $i < $s_max; $i++
) {
165 if ($this->final[$i +
$j * $this->qr_size +
1]) {
166 $mpdf->Rect($x +
($i - $s_min) * $s, $y +
($j - $s_min) * $s, $s, $s, 'F');
175 * permet d'afficher le QRcode au format HTML, à utiliser avec un style CSS
177 * @return boolean true;
179 public function displayHTML()
181 if ($this->disable_border
) {
183 $s_max = $this->qr_size
- 4;
186 $s_max = $this->qr_size
;
188 echo '<table class="qr" cellpadding="0" cellspacing="0">' . "\n";
189 for ($y = $s_min; $y < $s_max; $y++
) {
191 for ($x = $s_min; $x < $s_max; $x++
) {
192 echo '<td class="' . ($this->final[$x +
$y * $this->qr_size +
1] ?
'on' : 'off') . '"></td>';
202 * permet d'obtenir une image PNG
204 * @param float taille du qrcode
205 * @param array couleur du background (R,V,B)
206 * @param array couleur des cases et du border (R,V,B)
207 * @param string nom du fichier de sortie. si null : sortie directe
208 * @param integer qualité de 0 (aucune compression) a 9
209 * @return boolean true;
211 public function displayPNG($w = 100, $background = [255, 255, 255], $color = [0, 0, 0], $filename = null, $quality = 0)
213 if ($this->disable_border
) {
215 $s_max = $this->qr_size
- 4;
218 $s_max = $this->qr_size
;
221 $s = $size / ($s_max - $s_min);
224 $im = imagecreatetruecolor($size, $size);
225 $c_case = imagecolorallocate($im, $color[0], $color[1], $color[2]);
226 $c_back = imagecolorallocate($im, $background[0], $background[1], $background[2]);
227 imagefilledrectangle($im, 0, 0, $size, $size, $c_back);
229 for ($j = $s_min; $j < $s_max; $j++
) {
230 for ($i = $s_min; $i < $s_max; $i++
) {
231 if ($this->final[$i +
$j * $this->qr_size +
1]) {
232 imagefilledrectangle($im, ($i - $s_min) * $s, ($j - $s_min) * $s, ($i - $s_min +
1) * $s - 1, ($j - $s_min +
1) * $s - 1, $c_case);
238 imagepng($im, $filename, $quality);
240 header("Content-type: image/png");
248 private function addData($val, $bit, $next = true)
250 $this->data_val
[$this->data_cur
] = $val;
251 $this->data_bit
[$this->data_cur
] = $bit;
255 return $this->data_cur
- 1;
257 return $this->data_cur
;
261 private function encode()
263 // conversion des datas
264 if (preg_match('/[^0-9]/', $this->value
)) {
265 if (preg_match('/[^0-9A-Z \$\*\%\+\-\.\/\:]/', $this->value
)) {
268 $this->addData(4, 4);
270 // taille. il faut garder l'indice, car besoin de correction
271 $this->data_num
= $this->addData($this->length
, 8); /* #version 1-9 */
272 $data_num_correction = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8];
275 for ($i = 0; $i < $this->length
; $i++
) {
276 $this->addData(ord(substr($this->value
, $i, 1)), 8);
280 $this->type
= 'alphanum';
281 $this->addData(2, 4);
283 // taille. il faut garder l'indice, car besoin de correction
284 $this->data_num
= $this->addData($this->length
, 9); /* #version 1-9 */
285 $data_num_correction = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4];
289 '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9,
290 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14, 'F' => 15, 'G' => 16, 'H' => 17, 'I' => 18, 'J' => 19, 'K' => 20, 'L' => 21, 'M' => 22,
291 'N' => 23, 'O' => 24, 'P' => 25, 'Q' => 26, 'R' => 27, 'S' => 28, 'T' => 29, 'U' => 30, 'V' => 31, 'W' => 32, 'X' => 33, 'Y' => 34, 'Z' => 35,
292 ' ' => 36, '$' => 37, '%' => 38, '*' => 39, '+' => 40, '-' => 41, '.' => 42, '/' => 43, ':' => 44];
294 for ($i = 0; $i < $this->length
; $i++
) {
296 $this->addData($an_hash[substr($this->value
, $i, 1)], 6, false);
298 $this->addData($this->data_val
[$this->data_cur
] * 45 +
$an_hash[substr($this->value
, $i, 1)], 11, true);
303 if (isset($this->data_bit
[$this->data_cur
])) {
310 $this->addData(1, 4);
312 //taille. il faut garder l'indice, car besoin de correction
313 $this->data_num
= $this->addData($this->length
, 10); /* #version 1-9 */
314 $data_num_correction = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4];
317 for ($i = 0; $i < $this->length
; $i++
) {
319 $this->addData(substr($this->value
, $i, 1), 4, false);
320 } else if (($i %
3) == 1) {
321 $this->addData($this->data_val
[$this->data_cur
] * 10 +
substr($this->value
, $i, 1), 7, false);
323 $this->addData($this->data_val
[$this->data_cur
] * 10 +
substr($this->value
, $i, 1), 10);
327 if (isset($this->data_bit
[$this->data_cur
])) {
332 // calcul du nombre de bits
333 $this->data_bits
= 0;
334 foreach ($this->data_bit
as $bit) {
335 $this->data_bits +
= $bit;
339 $ec_hash = ['L' => 1, 'M' => 0, 'Q' => 3, 'H' => 2];
340 $this->ec
= $ec_hash[$this->level
];
342 // tableau de taille limite de bits
344 0, 128, 224, 352, 512, 688, 864, 992, 1232, 1456, 1728, 2032, 2320, 2672, 2920, 3320, 3624, 4056, 4504, 5016, 5352,
345 5712, 6256, 6880, 7312, 8000, 8496, 9024, 9544, 10136, 10984, 11640, 12328, 13048, 13800, 14496, 15312, 15936, 16816, 17728, 18672,
347 152, 272, 440, 640, 864, 1088, 1248, 1552, 1856, 2192, 2592, 2960, 3424, 3688, 4184, 4712, 5176, 5768, 6360, 6888,
348 7456, 8048, 8752, 9392, 10208, 10960, 11744, 12248, 13048, 13880, 14744, 15640, 16568, 17528, 18448, 19472, 20528, 21616, 22496, 23648,
350 72, 128, 208, 288, 368, 480, 528, 688, 800, 976, 1120, 1264, 1440, 1576, 1784, 2024, 2264, 2504, 2728, 3080,
351 3248, 3536, 3712, 4112, 4304, 4768, 5024, 5288, 5608, 5960, 6344, 6760, 7208, 7688, 7888, 8432, 8768, 9136, 9776, 10208,
353 104, 176, 272, 384, 496, 608, 704, 880, 1056, 1232, 1440, 1648, 1952, 2088, 2360, 2600, 2936, 3176, 3560, 3880,
354 4096, 4544, 4912, 5312, 5744, 6032, 6464, 6968, 7288, 7880, 8264, 8920, 9368, 9848, 10288, 10832, 11408, 12016, 12656, 13328,
357 // determination automatique de la version necessaire
359 $i = 1 +
40 * $this->ec
;
362 if ($max_bits[$i] >= $this->data_bits +
$data_num_correction[$this->version
]) {
363 $this->max_data_bit
= $max_bits[$i];
370 // verification max version
371 if ($this->version
> $this->version_mx
) {
372 throw new \Mpdf\QrCode\
QrCodeException('QrCode version too large');
375 // correctif sur le nombre de bits du strlen de la valeur
376 $this->data_bits +
= $data_num_correction[$this->version
];
377 $this->data_bit
[$this->data_num
] +
= $data_num_correction[$this->version
];
378 $this->max_data_word
= ($this->max_data_bit
/ 8);
380 // nombre de mots maximal
381 $max_words_array = [0, 26, 44, 70, 100, 134, 172, 196, 242, 292, 346, 404, 466, 532, 581, 655, 733, 815, 901, 991, 1085, 1156,
382 1258, 1364, 1474, 1588, 1706, 1828, 1921, 2051, 2185, 2323, 2465, 2611, 2761, 2876, 3034, 3196, 3362, 3532, 3706];
384 $this->max_word
= $max_words_array[$this->version
];
385 $this->size
= 17 +
4 * $this->version
;
389 unset($data_num_correction);
390 unset($max_words_array);
394 if ($this->data_bits
<= $this->max_data_bit
- 4) {
395 $this->addData(0, 4);
396 } elseif ($this->data_bits
< $this->max_data_bit
) {
397 $this->addData(0, $this->max_data_bit
- $this->data_bits
);
398 } elseif ($this->data_bits
> $this->max_data_bit
) {
399 throw new \Mpdf\QrCode\
QrCodeException('QrCode data overflow error');
402 // construction des mots de 8 bit
403 $this->data_word
= [];
404 $this->data_word
[0] = 0;
408 for ($i = 0; $i < $this->data_cur
; $i++
) {
409 $buffer_val = $this->data_val
[$i];
410 $buffer_bit = $this->data_bit
[$i];
414 if ($remaining_bit > $buffer_bit) {
415 $this->data_word
[$nb_word] = ((@$this->data_word
[$nb_word] << $buffer_bit) |
$buffer_val);
416 $remaining_bit -= $buffer_bit;
419 $buffer_bit -= $remaining_bit;
420 $this->data_word
[$nb_word] = ((@$this->data_word
[$nb_word] << $remaining_bit) |
($buffer_val >> $buffer_bit));
423 if ($buffer_bit == 0) {
426 $buffer_val = ($buffer_val & ((1 << $buffer_bit) - 1));
429 if ($nb_word < $this->max_data_word
- 1) {
430 $this->data_word
[$nb_word] = 0;
437 // completion du dernier mot si incomplet
438 if ($remaining_bit < 8) {
439 $this->data_word
[$nb_word] = $this->data_word
[$nb_word] << $remaining_bit;
444 // remplissage du reste
445 if ($nb_word < $this->max_data_word
- 1) {
447 while ($nb_word < $this->max_data_word
- 1) {
450 $this->data_word
[$nb_word] = 236;
452 $this->data_word
[$nb_word] = 17;
459 private function loadECC()
461 $matrix_remain_bit = [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];
462 $this->matrix_remain
= $matrix_remain_bit[$this->version
];
463 unset($matrix_remain_bit);
465 // lecture du fichier : data file of geometry & mask for version V ,ecc level N
466 $this->byte_num
= $this->matrix_remain +
8 * $this->max_word
;
467 $filename = __DIR__
. "/data/qrv" . $this->version
. "_" . $this->ec
. ".dat";
468 $fp1 = fopen($filename, "rb");
469 $this->matrix_x_array
= unpack("C*", fread($fp1, $this->byte_num
));
470 $this->matrix_y_array
= unpack("C*", fread($fp1, $this->byte_num
));
471 $this->mask_array
= unpack("C*", fread($fp1, $this->byte_num
));
472 $this->format_information_x2
= unpack("C*", fread($fp1, 15));
473 $this->format_information_y2
= unpack("C*", fread($fp1, 15));
474 $this->rs_ecc_codewords
= ord(fread($fp1, 1));
475 $this->rs_block_order
= unpack("C*", fread($fp1, 128));
477 $this->format_information_x1
= [0, 1, 2, 3, 4, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8];
478 $this->format_information_y1
= [8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 4, 3, 2, 1, 0];
481 private function makeECC()
483 // lecture du fichier : data file of caluclatin tables for RS encoding
484 $rs_cal_table_array = [];
485 $filename = __DIR__
. "/data/rsc" . $this->rs_ecc_codewords
. ".dat";
486 $fp0 = fopen($filename, "rb");
487 for ($i = 0; $i < 256; $i++
) {
488 $rs_cal_table_array[$i] = fread($fp0, $this->rs_ecc_codewords
);
492 $max_data_codewords = count($this->data_word
);
496 $rs_block_number = 0;
498 for ($i = 0; $i < $max_data_codewords; $i++
) {
499 $rs_temp[$rs_block_number] .= chr($this->data_word
[$i]);
501 if ($j >= $this->rs_block_order
[$rs_block_number +
1] - $this->rs_ecc_codewords
) {
504 $rs_temp[$rs_block_number] = "";
509 $rs_block_order_num = count($this->rs_block_order
);
511 for ($rs_block_number = 0; $rs_block_number < $rs_block_order_num; $rs_block_number++
) {
512 $rs_codewords = $this->rs_block_order
[$rs_block_number +
1];
513 $rs_data_codewords = $rs_codewords - $this->rs_ecc_codewords
;
515 $rstemp = $rs_temp[$rs_block_number] . str_repeat(chr(0), $this->rs_ecc_codewords
);
516 $padding_data = str_repeat(chr(0), $rs_data_codewords);
518 $j = $rs_data_codewords;
520 $first = ord(substr($rstemp, 0, 1));
523 $left_chr = substr($rstemp, 1);
524 $cal = $rs_cal_table_array[$first] . $padding_data;
525 $rstemp = $left_chr ^
$cal;
527 $rstemp = substr($rstemp, 1);
532 $this->data_word
= array_merge($this->data_word
, unpack("C*", $rstemp));
536 private function makeMatrix()
539 $this->matrix
= array_fill(0, $this->size
, array_fill(0, $this->size
, 0));
542 for ($i = 0; $i < $this->max_word
; $i++
) {
543 $word = $this->data_word
[$i];
544 for ($j = 8; $j > 0; $j--) {
545 $bit_pos = ($i << 3) +
$j;
546 $this->matrix
[$this->matrix_x_array
[$bit_pos]][$this->matrix_y_array
[$bit_pos]] = ((255 * ($word & 1)) ^
$this->mask_array
[$bit_pos]);
551 for ($k = $this->matrix_remain
; $k > 0; $k--) {
552 $bit_pos = $k +
($this->max_word
<< 3);
553 $this->matrix
[$this->matrix_x_array
[$bit_pos]][$this->matrix_y_array
[$bit_pos]] = (255 ^
$this->mask_array
[$bit_pos]);
557 $min_demerit_score = 0;
561 while ($k < $this->size
) {
563 while ($l < $this->size
) {
564 $hor_master = $hor_master . chr($this->matrix
[$l][$k]);
565 $ver_master = $ver_master . chr($this->matrix
[$k][$l]);
572 $all_matrix = $this->size
* $this->size
;
578 $bit_r = (~
$bit) & 255;
579 $bit_mask = str_repeat(chr($bit), $all_matrix);
580 $hor = $hor_master & $bit_mask;
581 $ver = $ver_master & $bit_mask;
583 $ver_shift1 = $ver . str_repeat(chr(170), $this->size
);
584 $ver_shift2 = str_repeat(chr(170), $this->size
) . $ver;
585 $ver_shift1_0 = $ver . str_repeat(chr(0), $this->size
);
586 $ver_shift2_0 = str_repeat(chr(0), $this->size
) . $ver;
587 $ver_or = chunk_split(~
($ver_shift1 |
$ver_shift2), $this->size
, chr(170));
588 $ver_and = chunk_split(~
($ver_shift1_0 & $ver_shift2_0), $this->size
, chr(170));
590 $hor = chunk_split(~
$hor, $this->size
, chr(170));
591 $ver = chunk_split(~
$ver, $this->size
, chr(170));
592 $hor = $hor . chr(170) . $ver;
594 $n1_search = "/" . str_repeat(chr(255), 5) . "+|" . str_repeat(chr($bit_r), 5) . "+/";
595 $n3_search = chr($bit_r) . chr(255) . chr($bit_r) . chr($bit_r) . chr($bit_r) . chr(255) . chr($bit_r);
597 $demerit_n3 = substr_count($hor, $n3_search) * 40;
598 $demerit_n4 = floor(abs(((100 * (substr_count($ver, chr($bit_r)) / ($this->byte_num
))) - 50) / 5)) * 10;
600 $n2_search1 = "/" . chr($bit_r) . chr($bit_r) . "+/";
601 $n2_search2 = "/" . chr(255) . chr(255) . "+/";
603 preg_match_all($n2_search1, $ver_and, $ptn_temp);
604 foreach ($ptn_temp[0] as $str_temp) {
605 $demerit_n2 +
= (strlen($str_temp) - 1);
608 preg_match_all($n2_search2, $ver_or, $ptn_temp);
609 foreach ($ptn_temp[0] as $str_temp) {
610 $demerit_n2 +
= (strlen($str_temp) - 1);
616 preg_match_all($n1_search, $hor, $ptn_temp);
617 foreach ($ptn_temp[0] as $str_temp) {
618 $demerit_n1 +
= (strlen($str_temp) - 2);
620 $demerit_score = $demerit_n1 +
$demerit_n2 +
$demerit_n3 +
$demerit_n4;
622 if ($demerit_score <= $min_demerit_score ||
$i == 0) {
624 $min_demerit_score = $demerit_score;
630 $mask_content = 1 << $mask_number;
632 $format_information_value = (($this->ec
<< 3) |
$mask_number);
633 $format_information_array = ["101010000010010", "101000100100101",
634 "101111001111100", "101101101001011", "100010111111001", "100000011001110",
635 "100111110010111", "100101010100000", "111011111000100", "111001011110011",
636 "111110110101010", "111100010011101", "110011000101111", "110001100011000",
637 "110110001000001", "110100101110110", "001011010001001", "001001110111110",
638 "001110011100111", "001100111010000", "000011101100010", "000001001010101",
639 "000110100001100", "000100000111011", "011010101011111", "011000001101000",
640 "011111100110001", "011101000000110", "010010010110100", "010000110000011",
641 "010111011011010", "010101111101101"];
643 for ($i = 0; $i < 15; $i++
) {
644 $content = substr($format_information_array[$format_information_value], $i, 1);
646 $this->matrix
[$this->format_information_x1
[$i]][$this->format_information_y1
[$i]] = $content * 255;
647 $this->matrix
[$this->format_information_x2
[$i +
1]][$this->format_information_y2
[$i +
1]] = $content * 255;
650 $this->final = unpack("C*", file_get_contents(__DIR__
. '/data/modele' . $this->version
. '.dat'));
651 $this->qr_size
= $this->size +
8;
653 for ($x = 0; $x < $this->size
; $x++
) {
654 for ($y = 0; $y < $this->size
; $y++
) {
655 if ($this->matrix
[$x][$y] & $mask_content) {
656 $this->final[($x +
4) +
($y +
4) * $this->qr_size +
1] = true;