4 * (QR Code is registered trademark of DENSO WAVE INCORPORATED | http://www.denso-wave.com/qrcode/)
5 * Fortement inspiré de "QRcode image PHP scripts version 0.50g (C)2000-2005,Y.Swetake"
7 * Distribué sous la licence LGPL.
9 * @author Laurent MINGUET <webmaster@spipu.net>
13 if (!defined('__CLASS_QRCODE__'))
15 define('__CLASS_QRCODE__', '0.99');
19 private $version_mx = 40; // numero de version maximal autorisé
20 private $type = 'bin'; // type de donnée
21 private $level = 'L'; // ECC
22 private $value = ''; // valeur a encoder
23 private $length = 0; // taille de la valeur
24 private $version = 0; // version
25 private $size = 0; // dimension de la zone data
26 private $qr_size = 0; // dimension du QRcode
28 private $data_bit = array(); // nb de bit de chacune des valeurs
29 private $data_val = array(); // liste des valeurs de bit différents
30 private $data_word = array(); // liste des valeurs tout ramené à 8bit
31 private $data_cur = 0; // position courante
32 private $data_num = 0; // position de la dimension
33 private $data_bits = 0; // nom de bit au total
34 private $max_data_bit = 0; // lilmite de nombre de bit maximal pour les datas
35 private $max_data_word = 0; // lilmite de nombre de mot maximal pour les datas
36 private $max_word = 0; // lilmite de nombre de mot maximal en global
39 private $matrix = array();
40 private $matrix_remain = 0;
41 private $matrix_x_array = array();
42 private $matrix_y_array = array();
43 private $mask_array = array();
44 private $format_information_x1 = array();
45 private $format_information_y1 = array();
46 private $format_information_x2 = array();
47 private $format_information_y2 = array();
48 private $rs_block_order = array();
49 private $rs_ecc_codewords = 0;
50 private $byte_num = 0;
52 private $final = array();
53 private $disable_border = false;
59 * @param string message a encoder
60 * @param string niveau de correction d'erreur (ECC) : L, M, Q, H
63 public function __construct($value, $level='L')
65 if (!in_array($level, array('L', 'M', 'Q', 'H')))
66 $this->ERROR('ECC non reconnu : L, M, Q, H');
68 $this->length
= strlen($value);
70 $this->ERROR('pas de data...');
72 $this->level
= $level;
73 $this->value
= &$value;
75 $this->data_bit
= array();
76 $this->data_val
= array();
87 * permet de recuperer la taille du QRcode (le nombre de case de côté)
89 * @return int size of qrcode
91 public function getQrSize()
93 if ($this->disable_border
)
94 return $this->qr_size
-8;
96 return $this->qr_size
;
99 public function disableBorder()
101 $this->disable_border
= true;
105 * permet d'afficher le QRcode dans un pdf via FPDF
107 * @param FPDF objet fpdf
108 * @param float position X
109 * @param float position Y
110 * @param float taille du qrcode
111 * @param array couleur du background (R,V,B)
112 * @param array couleur des cases et du border (R,V,B)
113 * @return boolean true;
115 public function displayFPDF(&$fpdf, $x, $y, $w, $background=array(255,255,255), $color=array(0,0,0))
118 $s = $size/$this->getQrSize();
120 $fpdf->SetDrawColor($color[0], $color[1], $color[2]);
121 $fpdf->SetFillColor($background[0], $background[1], $background[2]);
124 if ($this->disable_border
)
127 $s_max = $this->qr_size
-4;
128 $fpdf->Rect($x, $y, $size, $size, 'F');
133 $s_max = $this->qr_size
;
134 $fpdf->Rect($x, $y, $size, $size, 'FD');
137 $fpdf->SetFillColor($color[0], $color[1], $color[2]);
138 for($j=$s_min; $j<$s_max; $j++
)
139 for($i=$s_min; $i<$s_max; $i++
)
140 if ($this->final[$i +
$j*$this->qr_size+
1])
141 $fpdf->Rect($x+
($i-$s_min)*$s, $y+
($j-$s_min)*$s, $s, $s, 'F');
147 * permet d'afficher le QRcode au format HTML, à utiliser avec un style CSS
149 * @return boolean true;
151 public function displayHTML()
153 if ($this->disable_border
)
156 $s_max = $this->qr_size
-4;
161 $s_max = $this->qr_size
;
163 echo '<table class="qr" cellpadding="0" cellspacing="0">'."\n";
164 for($y=$s_min; $y<$s_max; $y++
)
167 for($x=$s_min; $x<$s_max; $x++
)
169 echo '<td class="'.($this->final[$x +
$y*$this->qr_size+
1] ?
'on' : 'off').'"></td>';
179 * permet d'obtenir une image PNG
181 * @param float taille du qrcode
182 * @param array couleur du background (R,V,B)
183 * @param array couleur des cases et du border (R,V,B)
184 * @param string nom du fichier de sortie. si null : sortie directe
185 * @param integer qualité de 0 (aucune compression) a 9
186 * @return boolean true;
188 public function displayPNG($w=100, $background=array(255,255,255), $color=array(0,0,0), $filename = null, $quality = 0)
190 if ($this->disable_border
)
193 $s_max = $this->qr_size
-4;
198 $s_max = $this->qr_size
;
201 $s = $size/($s_max-$s_min);
204 $im = imagecreatetruecolor($size,$size);
205 $c_case = imagecolorallocate($im,$color[0],$color[1],$color[2]);
206 $c_back = imagecolorallocate($im,$background[0],$background[1],$background[2]);
207 imagefilledrectangle($im,0,0,$size,$size,$c_back);
209 for($j=$s_min; $j<$s_max; $j++
)
210 for($i=$s_min; $i<$s_max; $i++
)
211 if ($this->final[$i +
$j*$this->qr_size+
1])
212 imagefilledrectangle($im,($i-$s_min)*$s,($j-$s_min)*$s,($i-$s_min+
1)*$s-1,($j-$s_min+
1)*$s-1,$c_case);
216 imagepng($im, $filename, $quality);
220 header("Content-type: image/png");
227 private function ERROR($msg)
229 echo 'ERROR : '.$msg;
233 private function addData($val, $bit, $next = true)
235 $this->data_val
[$this->data_cur
] = $val;
236 $this->data_bit
[$this->data_cur
] = $bit;
240 return $this->data_cur
-1;
244 return $this->data_cur
;
248 private function encode()
250 // conversion des datas
251 if (preg_match('/[^0-9]/',$this->value
))
253 if (preg_match('/[^0-9A-Z \$\*\%\+\-\.\/\:]/',$this->value
))
257 $this->addData(4, 4);
259 // taille. il faut garder l'indice, car besoin de correction
260 $this->data_num
= $this->addData($this->length
, 8); /* #version 1-9 */
261 $data_num_correction=array(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);
264 for ($i=0; $i<$this->length
; $i++
)
265 $this->addData(ord(substr($this->value
, $i, 1)), 8);
270 $this->type
= 'alphanum';
271 $this->addData(2, 4);
273 // taille. il faut garder l'indice, car besoin de correction
274 $this->data_num
= $this->addData($this->length
, 9); /* #version 1-9 */
275 $data_num_correction=array(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);
279 '0'=>0,'1'=>1,'2'=>2,'3'=>3,'4'=>4,'5'=>5,'6'=>6,'7'=>7,'8'=>8,'9'=>9,
280 '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,
281 '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,
282 ' '=>36,'$'=>37,'%'=>38,'*'=>39,'+'=>40,'-'=>41,'.'=>42,'/'=>43,':'=>44);
284 for ($i=0; $i<$this->length
; $i++
)
287 $this->addData($an_hash[substr($this->value
,$i,1)], 6, false);
289 $this->addData($this->data_val
[$this->data_cur
]*45+
$an_hash[substr($this->value
,$i,1)], 11, true);
293 if (isset($this->data_bit
[$this->data_cur
]))
301 $this->addData(1, 4);
303 //taille. il faut garder l'indice, car besoin de correction
304 $this->data_num
= $this->addData($this->length
, 10); /* #version 1-9 */
305 $data_num_correction=array(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);
308 for ($i=0; $i<$this->length
; $i++
)
311 $this->addData(substr($this->value
,$i,1), 4, false);
312 else if (($i %
3)==1)
313 $this->addData($this->data_val
[$this->data_cur
]*10+
substr($this->value
,$i,1), 7, false);
315 $this->addData($this->data_val
[$this->data_cur
]*10+
substr($this->value
,$i,1), 10);
318 if (isset($this->data_bit
[$this->data_cur
]))
324 // calcul du nombre de bits
326 foreach($this->data_bit
as $bit)
327 $this->data_bits+
= $bit;
330 $ec_hash = array('L'=>1, 'M'=>0, 'Q'=>3, 'H'=>2);
331 $this->ec
= $ec_hash[$this->level
];
333 // tableau de taille limite de bits
335 0,128,224,352,512,688,864,992,1232,1456,1728,2032,2320,2672,2920,3320,3624,4056,4504,5016,5352,
336 5712,6256,6880,7312,8000,8496,9024,9544,10136,10984,11640,12328,13048,13800,14496,15312,15936,16816,17728,18672,
338 152,272,440,640,864,1088,1248,1552,1856,2192,2592,2960,3424,3688,4184,4712,5176,5768,6360,6888,
339 7456,8048,8752,9392,10208,10960,11744,12248,13048,13880,14744,15640,16568,17528,18448,19472,20528,21616,22496,23648,
341 72,128,208,288,368,480,528,688,800,976,1120,1264,1440,1576,1784,2024,2264,2504,2728,3080,
342 3248,3536,3712,4112,4304,4768,5024,5288,5608,5960,6344,6760,7208,7688,7888,8432,8768,9136,9776,10208,
344 104,176,272,384,496,608,704,880,1056,1232,1440,1648,1952,2088,2360,2600,2936,3176,3560,3880,
345 4096,4544,4912,5312,5744,6032,6464,6968,7288,7880,8264,8920,9368,9848,10288,10832,11408,12016,12656,13328
348 // determination automatique de la version necessaire
354 if ($max_bits[$i]>=$this->data_bits+
$data_num_correction[$this->version
])
356 $this->max_data_bit
=$max_bits[$i];
363 // verification max version
364 if ($this->version
>$this->version_mx
)
365 $this->ERROR('too large version.');
367 // correctif sur le nombre de bits du strlen de la valeur
368 $this->data_bits+
=$data_num_correction[$this->version
];
369 $this->data_bit
[$this->data_num
]+
=$data_num_correction[$this->version
];
370 $this->max_data_word
= ($this->max_data_bit
/8);
372 // nombre de mots maximal
373 $max_words_array=array(0,26,44,70,100,134,172,196,242,292,346,404,466,532,581,655,733,815,901,991,1085,1156,
374 1258,1364,1474,1588,1706,1828,1921,2051,2185,2323,2465,2611,2761,2876,3034,3196,3362,3532,3706);
375 $this->max_word
= $max_words_array[$this->version
];
376 $this->size
= 17 +
4*$this->version
;
380 unset($data_num_correction);
381 unset($max_words_array);
385 if ($this->data_bits
<=$this->max_data_bit
-4)
386 $this->addData(0, 4);
387 elseif ($this->data_bits
<$this->max_data_bit
)
388 $this->addData(0, $this->max_data_bit
-$this->data_bits
);
389 elseif ($this->data_bits
>$this->max_data_bit
)
390 $this->ERROR('Overflow error');
392 // construction des mots de 8 bit
393 $this->data_word
= array();
394 $this->data_word
[0] = 0;
398 for($i=0; $i<$this->data_cur
; $i++
)
400 $buffer_val=$this->data_val
[$i];
401 $buffer_bit=$this->data_bit
[$i];
406 if ($remaining_bit>$buffer_bit)
408 $this->data_word
[$nb_word]=((@$this->data_word
[$nb_word]<<$buffer_bit) |
$buffer_val);
409 $remaining_bit-=$buffer_bit;
414 $buffer_bit-=$remaining_bit;
415 $this->data_word
[$nb_word]=((@$this->data_word
[$nb_word] << $remaining_bit) |
($buffer_val >> $buffer_bit));
421 $buffer_val= ($buffer_val & ((1 << $buffer_bit)-1) );
423 if ($nb_word<$this->max_data_word
-1)
424 $this->data_word
[$nb_word]=0;
430 // completion du dernier mot si incomplet
431 if ($remaining_bit<8)
432 $this->data_word
[$nb_word]=$this->data_word
[$nb_word] << $remaining_bit;
436 // remplissage du reste
437 if ($nb_word<$this->max_data_word
-1)
440 while ($nb_word<$this->max_data_word
-1)
444 $this->data_word
[$nb_word]=236;
446 $this->data_word
[$nb_word]=17;
452 private function loadECC()
454 $matrix_remain_bit=array(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);
455 $this->matrix_remain
= $matrix_remain_bit[$this->version
];
456 unset($matrix_remain_bit);
458 // lecture du fichier : data file of geometry & mask for version V ,ecc level N
459 $this->byte_num
= $this->matrix_remain+
8*$this->max_word
;
460 $filename = dirname(__FILE__
)."/data/qrv".$this->version
."_".$this->ec
.".dat";
461 $fp1 = fopen ($filename, "rb");
462 $this->matrix_x_array
= unpack("C*", fread($fp1,$this->byte_num
));
463 $this->matrix_y_array
= unpack("C*", fread($fp1,$this->byte_num
));
464 $this->mask_array
= unpack("C*", fread($fp1,$this->byte_num
));
465 $this->format_information_x2
= unpack("C*", fread($fp1,15));
466 $this->format_information_y2
= unpack("C*", fread($fp1,15));
467 $this->rs_ecc_codewords
= ord(fread($fp1,1));
468 $this->rs_block_order
= unpack("C*", fread($fp1,128));
470 $this->format_information_x1
= array(0,1,2,3,4,5,7,8,8,8,8,8,8,8,8);
471 $this->format_information_y1
= array(8,8,8,8,8,8,8,8,7,5,4,3,2,1,0);
475 private function makeECC()
477 // lecture du fichier : data file of caluclatin tables for RS encoding
478 $rs_cal_table_array = array();
479 $filename = dirname(__FILE__
)."/data/rsc".$this->rs_ecc_codewords
.".dat";
480 $fp0 = fopen ($filename, "rb");
481 for($i=0; $i<256; $i++
)
482 $rs_cal_table_array[$i]=fread ($fp0,$this->rs_ecc_codewords
);
485 $max_data_codewords = count($this->data_word
);
491 for($i=0; $i<$max_data_codewords; $i++
)
493 $rs_temp[$rs_block_number].=chr($this->data_word
[$i]);
495 if ($j>=$this->rs_block_order
[$rs_block_number+
1]-$this->rs_ecc_codewords
)
499 $rs_temp[$rs_block_number]="";
504 $rs_block_order_num=count($this->rs_block_order
);
506 for($rs_block_number=0; $rs_block_number<$rs_block_order_num; $rs_block_number++
)
508 $rs_codewords=$this->rs_block_order
[$rs_block_number+
1];
509 $rs_data_codewords=$rs_codewords-$this->rs_ecc_codewords
;
511 $rstemp=$rs_temp[$rs_block_number].str_repeat(chr(0),$this->rs_ecc_codewords
);
512 $padding_data=str_repeat(chr(0),$rs_data_codewords);
514 $j=$rs_data_codewords;
517 $first=ord(substr($rstemp,0,1));
521 $left_chr=substr($rstemp,1);
522 $cal=$rs_cal_table_array[$first].$padding_data;
523 $rstemp=$left_chr ^
$cal;
526 $rstemp=substr($rstemp,1);
530 $this->data_word
=array_merge($this->data_word
,unpack("C*",$rstemp));
534 private function makeMatrix()
537 $this->matrix
= array_fill(0, $this->size
, array_fill(0, $this->size
, 0));
540 for($i=0; $i<$this->max_word
; $i++
)
542 $word = $this->data_word
[$i];
543 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--)
553 $bit_pos = $k +
( $this->max_word
<<3);
554 $this->matrix
[ $this->matrix_x_array
[$bit_pos] ][ $this->matrix_y_array
[$bit_pos] ] = ( 255 ^
$this->mask_array
[$bit_pos] );
558 $min_demerit_score=0;
562 while($k<$this->size
)
565 while($l<$this->size
)
567 $hor_master=$hor_master.chr($this->matrix
[$l][$k]);
568 $ver_master=$ver_master.chr($this->matrix
[$k][$l]);
575 $all_matrix=$this->size
* $this->size
;
583 $bit_mask=str_repeat(chr($bit),$all_matrix);
584 $hor = $hor_master & $bit_mask;
585 $ver = $ver_master & $bit_mask;
587 $ver_shift1=$ver.str_repeat(chr(170),$this->size
);
588 $ver_shift2=str_repeat(chr(170),$this->size
).$ver;
589 $ver_shift1_0=$ver.str_repeat(chr(0),$this->size
);
590 $ver_shift2_0=str_repeat(chr(0),$this->size
).$ver;
591 $ver_or=chunk_split(~
($ver_shift1 |
$ver_shift2),$this->size
,chr(170));
592 $ver_and=chunk_split(~
($ver_shift1_0 & $ver_shift2_0),$this->size
,chr(170));
594 $hor=chunk_split(~
$hor,$this->size
,chr(170));
595 $ver=chunk_split(~
$ver,$this->size
,chr(170));
596 $hor=$hor.chr(170).$ver;
598 $n1_search="/".str_repeat(chr(255),5)."+|".str_repeat(chr($bit_r),5)."+/";
599 $n3_search=chr($bit_r).chr(255).chr($bit_r).chr($bit_r).chr($bit_r).chr(255).chr($bit_r);
601 $demerit_n3=substr_count($hor,$n3_search)*40;
602 $demerit_n4=floor(abs(( (100* (substr_count($ver,chr($bit_r))/($this->byte_num
)) )-50)/5))*10;
604 $n2_search1="/".chr($bit_r).chr($bit_r)."+/";
605 $n2_search2="/".chr(255).chr(255)."+/";
607 preg_match_all($n2_search1,$ver_and,$ptn_temp);
608 foreach($ptn_temp[0] as $str_temp)
610 $demerit_n2+
=(strlen($str_temp)-1);
613 preg_match_all($n2_search2,$ver_or,$ptn_temp);
614 foreach($ptn_temp[0] as $str_temp)
616 $demerit_n2+
=(strlen($str_temp)-1);
622 preg_match_all($n1_search,$hor,$ptn_temp);
623 foreach($ptn_temp[0] as $str_temp)
625 $demerit_n1+
=(strlen($str_temp)-2);
627 $demerit_score=$demerit_n1+
$demerit_n2+
$demerit_n3+
$demerit_n4;
629 if ($demerit_score<=$min_demerit_score ||
$i==0)
632 $min_demerit_score=$demerit_score;
638 $mask_content=1 << $mask_number;
640 $format_information_value=(($this->ec
<< 3) |
$mask_number);
641 $format_information_array=array("101010000010010","101000100100101",
642 "101111001111100","101101101001011","100010111111001","100000011001110",
643 "100111110010111","100101010100000","111011111000100","111001011110011",
644 "111110110101010","111100010011101","110011000101111","110001100011000",
645 "110110001000001","110100101110110","001011010001001","001001110111110",
646 "001110011100111","001100111010000","000011101100010","000001001010101",
647 "000110100001100","000100000111011","011010101011111","011000001101000",
648 "011111100110001","011101000000110","010010010110100","010000110000011",
649 "010111011011010","010101111101101");
651 for($i=0; $i<15; $i++
)
653 $content=substr($format_information_array[$format_information_value],$i,1);
655 $this->matrix
[$this->format_information_x1
[$i]][$this->format_information_y1
[$i]]=$content * 255;
656 $this->matrix
[$this->format_information_x2
[$i+
1]][$this->format_information_y2
[$i+
1]]=$content * 255;
659 $this->final = unpack("C*", file_get_contents(dirname(__FILE__
).'/data/modele'.$this->version
.'.dat'));
660 $this->qr_size
= $this->size+
8;
662 for($x=0; $x<$this->size
; $x++
)
664 for($y=0; $y<$this->size
; $y++
)
666 if ($this->matrix
[$x][$y] & $mask_content)
667 $this->final[($x+
4) +
($y+
4)*$this->qr_size+
1] = true;