1<?php
2
3/**
4 * QrCode
5 * @author  Akhtar Khan <er.akhtarkhan@gmail.com>
6 * @link http://www.codeitnow.in
7 * @package https://github.com/codeitnowin/barcode-generator
8 */
9
10namespace CodeItNow\BarcodeBundle\Utils;
11use Exception;
12use ReflectionFunction;
13
14/**
15 * Generate QR Code.
16 */
17class QrCode
18{
19    /** @const int Error Correction Level Low (7%) */
20    const LEVEL_LOW = 1;
21
22    /** @const int Error Correction Level Medium (15%) */
23    const LEVEL_MEDIUM = 0;
24
25    /** @const int Error Correction Level Quartile (25%) */
26    const LEVEL_QUARTILE = 3;
27
28    /** @const int Error Correction Level High (30%) */
29    const LEVEL_HIGH = 2;
30
31    /** @const string Image type png */
32    const IMAGE_TYPE_PNG = 'png';
33
34    /** @const string Image type gif */
35    const IMAGE_TYPE_GIF = 'gif';
36
37    /** @const string Image type jpeg */
38    const IMAGE_TYPE_JPEG = 'jpeg';
39
40    /** @const string Image type wbmp */
41    const IMAGE_TYPE_WBMP = 'wbmp';
42
43    /** @const int Horizontal label alignment to the center of image */
44    const LABEL_HALIGN_CENTER = 0;
45
46    /** @const int Horizontal label alignment to the left side of image */
47    const LABEL_HALIGN_LEFT = 1;
48
49    /** @const int Horizontal label alignment to the left border of QR Code */
50    const LABEL_HALIGN_LEFT_BORDER = 2;
51
52    /** @const int Horizontal label alignment to the left side of QR Code */
53    const LABEL_HALIGN_LEFT_CODE = 3;
54
55    /** @const int Horizontal label alignment to the right side of image */
56    const LABEL_HALIGN_RIGHT = 4;
57
58    /** @const int Horizontal label alignment to the right border of QR Code */
59    const LABEL_HALIGN_RIGHT_BORDER = 5;
60
61    /** @const int Horizontal label alignment to the right side of QR Code */
62    const LABEL_HALIGN_RIGHT_CODE = 6;
63
64    /** @const int Vertical label alignment to the top */
65    const LABEL_VALIGN_TOP = 1;
66
67    /** @const int Vertical label alignment to the top and hide border */
68    const LABEL_VALIGN_TOP_NO_BORDER = 2;
69
70    /** @const int Vertical label alignment to the middle*/
71    const LABEL_VALIGN_MIDDLE = 3;
72
73    /** @const int Vertical label alignment to the bottom */
74    const LABEL_VALIGN_BOTTOM = 4;
75
76    /** @var string */
77    protected $text = '';
78
79    /** @var int */
80    protected $size = 0;
81
82    /** @var int */
83    protected $padding = 16;
84
85    /** @var bool */
86    protected $draw_quiet_zone = false;
87
88    /** @var bool */
89    protected $draw_border = false;
90
91    /** @var array */
92    protected $color_foreground = array('r' => 0, 'g' => 0, 'b' => 0, 'a' => 0);
93
94    /** @var array */
95    protected $color_background = array('r' => 255, 'g' => 255, 'b' => 255, 'a' => 0);
96
97    /** @var string */
98    protected $label = '';
99
100    /** @var int */
101    protected $label_font_size = 16;
102
103    /** @var string */
104    protected $label_font_path = '';
105
106    /** @var int */
107    protected $label_halign = self::LABEL_HALIGN_CENTER;
108
109    /** @var int */
110    protected $label_valign = self::LABEL_VALIGN_MIDDLE;
111
112    /** @var resource */
113    protected $image = null;
114
115    /** @var int */
116    protected $version;
117
118    /** @var int */
119    protected $error_correction = self::LEVEL_MEDIUM;
120
121    /** @var array */
122    protected $error_corrections_available = array(
123        self::LEVEL_LOW,
124        self::LEVEL_MEDIUM,
125        self::LEVEL_QUARTILE,
126        self::LEVEL_HIGH,
127    );
128
129    /** @var int */
130    protected $module_size;
131
132    /** @var string */
133    protected $image_type = self::IMAGE_TYPE_PNG;
134
135    /** @var array */
136    protected $image_types_available = array(
137        self::IMAGE_TYPE_GIF,
138        self::IMAGE_TYPE_PNG,
139        self::IMAGE_TYPE_JPEG,
140        self::IMAGE_TYPE_WBMP,
141    );
142
143    /** @var string */
144    protected $image_path;
145
146    /** @var string */
147    protected $path;
148
149    /** @var int */
150    protected $structure_append_n;
151
152    /** @var int */
153    protected $structure_append_m;
154
155    /** @var int */
156    protected $structure_append_parity;
157
158    /** @var string */
159    protected $structure_append_original_data;
160
161    /**
162     * Class constructor.
163     *
164     * @param string $text
165     */
166    public function __construct($text = '')
167    {
168        $this->setPath(dirname(__DIR__)."/Resources/data");
169        $this->setImagePath(dirname(__DIR__)."/Resources/image");
170        $this->setLabelFontPath(dirname(__DIR__)."/Resources/font/opensans.ttf");
171        $this->setText($text);
172    }
173
174    /**
175     * Set structure append.
176     *
177     * @param int    $n
178     * @param int    $m
179     * @param int    $parity        Parity
180     * @param string $original_data Original data
181     *
182     * @return QrCode
183     */
184    public function setStructureAppend($n, $m, $parity, $original_data)
185    {
186        $this->structure_append_n = $n;
187        $this->structure_append_m = $m;
188        $this->structure_append_parity = $parity;
189        $this->structure_append_original_data = $original_data;
190
191        return $this;
192    }
193
194    /**
195     * Set QR Code version.
196     *
197     * @param int $version QR Code version
198     *
199     * @return QrCode
200     */
201    public function setVersion($version)
202    {
203        if ($version <= 40 && $version >= 0) {
204            $this->version = $version;
205        }
206
207        return $this;
208    }
209
210    /**
211     * Return QR Code version.
212     *
213     * @return int
214     */
215    public function getVersion()
216    {
217        return $this->version;
218    }
219
220    /**
221     * Set QR Code error correction level.
222     *
223     * @param mixed $error_correction Error Correction Level
224     *
225     * @return QrCode
226     */
227    public function setErrorCorrection($error_correction)
228    {
229        if (!is_numeric($error_correction)) {
230            $level_constant = 'CodeItNow\BarcodeBundle\Utils\QrCode::LEVEL_'.strtoupper($error_correction);
231            $error_correction = constant($level_constant);
232        }
233
234        if (in_array($error_correction, $this->error_corrections_available)) {
235            $this->error_correction = $error_correction;
236        }
237
238        return $this;
239    }
240
241    /**
242     * Return QR Code error correction level.
243     *
244     * @return int
245     */
246    public function getErrorCorrection()
247    {
248        return $this->error_correction;
249    }
250
251    /**
252     * Set QR Code module size.
253     *
254     * @param int $module_size Module size
255     *
256     * @return QrCode
257     */
258    public function setModuleSize($module_size)
259    {
260        $this->module_size = $module_size;
261
262        return $this;
263    }
264
265    /**
266     * Return QR Code module size.
267     *
268     * @return int
269     */
270    public function getModuleSize()
271    {
272        return $this->module_size;
273    }
274
275    /**
276     * Set image type for rendering.
277     *
278     * @param string $image_type Image type
279     *
280     * @return QrCode
281     *
282     * @throws Exception
283     */
284    public function setImageType($image_type)
285    {
286        if (!in_array($image_type, $this->image_types_available)) {
287            throw new Exception('QRCode: image type '.$image_type.' is invalid.');
288        }
289
290        $this->image_type = $image_type;
291
292        return $this;
293    }
294
295    /**
296     * Return image type for rendering.
297     *
298     * @return string
299     */
300    public function getImageType()
301    {
302        return $this->image_type;
303    }
304
305    /**
306     * Set image type for rendering via extension.
307     *
308     * @param string $extension Image extension
309     *
310     * @return QrCode
311     */
312    public function setExtension($extension)
313    {
314        if ($extension == 'jpg') {
315            $this->setImageType('jpeg');
316        } else {
317            $this->setImageType($extension);
318        }
319
320        return $this;
321    }
322
323    /**
324     * Set path to the images directory.
325     *
326     * @param string $image_path Image directory
327     *
328     * @return QrCode
329     */
330    public function setImagePath($image_path)
331    {
332        $this->image_path = $image_path;
333
334        return $this;
335    }
336
337    /**
338     * Return path to the images directory.
339     *
340     * @return string
341     */
342    public function getImagePath()
343    {
344        return $this->image_path;
345    }
346
347    /**
348     * Set path to the data directory.
349     *
350     * @param string $path Data directory
351     *
352     * @return QrCode
353     */
354    public function setPath($path)
355    {
356        $this->path = $path;
357
358        return $this;
359    }
360
361    /**
362     * Return path to the data directory.
363     *
364     * @return string
365     */
366    public function getPath()
367    {
368        return $this->path;
369    }
370
371    /**
372     * Set text to hide in QR Code.
373     *
374     * @param string $text Text to hide
375     *
376     * @return QrCode
377     */
378    public function setText($text)
379    {
380        $this->text = $text;
381
382        return $this;
383    }
384
385    /**
386     * Return text that will be hid in QR Code.
387     *
388     * @return string
389     */
390    public function getText()
391    {
392        return $this->text;
393    }
394
395    /**
396     * Set QR Code size (width).
397     *
398     * @param int $size Width of the QR Code
399     *
400     * @return QrCode
401     */
402    public function setSize($size)
403    {
404        $this->size = $size;
405
406        return $this;
407    }
408
409    /**
410     * Return QR Code size (width).
411     *
412     * @return int
413     */
414    public function getSize()
415    {
416        return $this->size;
417    }
418
419    /**
420     * Set padding around the QR Code.
421     *
422     * @param int $padding Padding around QR Code
423     *
424     * @return QrCode
425     */
426    public function setPadding($padding)
427    {
428        $this->padding = $padding;
429
430        return $this;
431    }
432
433    /**
434     * Return padding around the QR Code.
435     *
436     * @return int
437     */
438    public function getPadding()
439    {
440        return $this->padding;
441    }
442
443    /**
444     * Set draw required four-module wide margin.
445     *
446     * @param bool $draw_quiet_zone State of required four-module wide margin drawing
447     *
448     * @return QrCode
449     */
450    public function setDrawQuietZone($draw_quiet_zone)
451    {
452        $this->draw_quiet_zone = $draw_quiet_zone;
453
454        return $this;
455    }
456
457    /**
458     * Return draw required four-module wide margin.
459     *
460     * @return bool
461     */
462    public function getDrawQuietZone()
463    {
464        return $this->draw_quiet_zone;
465    }
466
467    /**
468     * Set draw border around QR Code.
469     *
470     * @param bool $draw_border State of border drawing
471     *
472     * @return QrCode
473     */
474    public function setDrawBorder($draw_border)
475    {
476        $this->draw_border = $draw_border;
477
478        return $this;
479    }
480
481    /**
482     * Return draw border around QR Code.
483     *
484     * @return bool
485     */
486    public function getDrawBorder()
487    {
488        return $this->draw_border;
489    }
490
491    /**
492     * Set QR Code label (text).
493     *
494     * @param int|string $label Label to print under QR code
495     *
496     * @return QrCode
497     */
498    public function setLabel($label)
499    {
500        $this->label = $label;
501
502        return $this;
503    }
504
505    /**
506     * Return QR Code label (text).
507     *
508     * @return string
509     */
510    public function getLabel()
511    {
512        return $this->label;
513    }
514
515    /**
516     * Set QR Code label font size.
517     *
518     * @param int $label_font_size Font size of the QR code label
519     *
520     * @return QrCode
521     */
522    public function setLabelFontSize($label_font_size)
523    {
524        $this->label_font_size = $label_font_size;
525
526        return $this;
527    }
528
529    /**
530     * Return QR Code label font size.
531     *
532     * @return int
533     */
534    public function getLabelFontSize()
535    {
536        return $this->label_font_size;
537    }
538
539    /**
540     * Set QR Code label font path.
541     *
542     * @param int $label_font_path Path to the QR Code label's TTF font file
543     *
544     * @return QrCode
545     */
546    public function setLabelFontPath($label_font_path)
547    {
548        $this->label_font_path = $label_font_path;
549
550        return $this;
551    }
552
553    /**
554     * Return path to the QR Code label's TTF font file.
555     *
556     * @return string
557     */
558    public function getLabelFontPath()
559    {
560        return $this->label_font_path;
561    }
562
563    /**
564     * Set label horizontal alignment.
565     *
566     * @param int $label_halign Label horizontal alignment
567     *
568     * @return QrCode
569     */
570    public function setLabelHalign($label_halign)
571    {
572        $this->label_halign = $label_halign;
573
574        return $this;
575    }
576
577    /**
578     * Return label horizontal alignment.
579     *
580     * @return int
581     */
582    public function getLabelHalign()
583    {
584        return $this->label_halign;
585    }
586
587    /**
588     * Set label vertical alignment.
589     *
590     * @param int $label_valign Label vertical alignment
591     *
592     * @return QrCode
593     */
594    public function setLabelValign($label_valign)
595    {
596        $this->label_valign = $label_valign;
597
598        return $this;
599    }
600
601    /**
602     * Return label vertical alignment.
603     *
604     * @return int
605     */
606    public function getLabelValign()
607    {
608        return $this->label_valign;
609    }
610
611    /**
612     * Set foreground color of the QR Code.
613     *
614     * @param array $color_foreground RGB color
615     *
616     * @return QrCode
617     */
618    public function setForegroundColor($color_foreground)
619    {
620        if (!isset($color_foreground['a'])) {
621            $color_foreground['a'] = 0;
622        }
623
624        $this->color_foreground = $color_foreground;
625
626        return $this;
627    }
628
629    /**
630     * Return foreground color of the QR Code.
631     *
632     * @return array
633     */
634    public function getForegroundColor()
635    {
636        return $this->color_foreground;
637    }
638
639    /**
640     * Set background color of the QR Code.
641     *
642     * @param array $color_background RGB color
643     *
644     * @return QrCode
645     */
646    public function setBackgroundColor($color_background)
647    {
648        if (!isset($color_background['a'])) {
649            $color_background['a'] = 0;
650        }
651
652        $this->color_background = $color_background;
653
654        return $this;
655    }
656
657    /**
658     * Return background color of the QR Code.
659     *
660     * @return array
661     */
662    public function getBackgroundColor()
663    {
664        return $this->color_background;
665    }
666
667    /**
668     * Return the image resource.
669     *
670     * @return resource
671     */
672    public function getImage()
673    {
674        if (empty($this->image)) {
675            $this->create();
676        }
677
678        return $this->image;
679    }
680
681    /**
682     * Return the data URI.
683     *
684     * @return string
685     */
686    public function getDataUri()
687    {
688        if (empty($this->image)) {
689            $this->create();
690        }
691
692        ob_start();
693        call_user_func('image'.$this->image_type, $this->image);
694        $contents = ob_get_clean();
695
696        return 'data:image/'.$this->image_type.';base64,'.base64_encode($contents);
697    }
698
699    /**
700     * Render the QR Code then save it to given file name.
701     *
702     * @param string $filename File name of the QR Code
703     *
704     * @return QrCode
705     */
706    public function save($filename)
707    {
708        $this->render($filename);
709
710        return $this;
711    }
712
713    /**
714     * Render the QR Code then save it to given file name or
715     * output it to the browser when file name omitted.
716     *
717     * @param null|string $filename File name of the QR Code
718     * @param null|string $format   Format of the file (png, jpeg, jpg, gif, wbmp)
719     *
720     * @throws Exception
721     * @throws Exception
722     *
723     * @return QrCode
724     */
725    public function render($filename = null, $format = 'png')
726    {
727        $this->create();
728
729        if ($format == 'jpg') {
730            $format = 'jpeg';
731        }
732
733        if (!in_array($format, $this->image_types_available)) {
734            $format = $this->image_type;
735        }
736
737        if (!function_exists('image'.$format)) {
738            throw new Exception('QRCode: function image'.$format.' does not exists.');
739        }
740
741        if ($filename === null) {
742            $success = call_user_func('image'.$format, $this->image);
743        } else {
744            $success = call_user_func_array('image'.$format, array($this->image, $filename));
745        }
746
747        if ($success === false) {
748            throw new Exception('QRCode: function image'.$format.' failed.');
749        }
750
751        return $this;
752    }
753
754    /**
755     * Generate base64 image
756     * @return type
757     */
758    public function generate(){
759        $image = $this->get();
760        return base64_encode($image);
761    }
762
763    /**
764     * Returns the content type corresponding to the image type.
765     *
766     * @return string
767     */
768    public function getContentType()
769    {
770        $contentType = 'image/'.$this->image_type;
771
772        return $contentType;
773    }
774
775    /**
776     * Create QR Code and return its content.
777     *
778     * @param string|null $format Image type (gif, png, wbmp, jpeg)
779     *
780     * @throws Exception
781     * @throws Exception
782     *
783     * @return string
784     */
785    public function get($format = null)
786    {
787        $this->create();
788
789        if ($format == 'jpg') {
790            $format = 'jpeg';
791        }
792
793        if (!in_array($format, $this->image_types_available)) {
794            $format = $this->image_type;
795        }
796
797        if (!function_exists('image'.$format)) {
798            throw new Exception('QRCode: function image'.$format.' does not exists.');
799        }
800
801        ob_start();
802        $success = call_user_func('image'.$format, $this->image);
803
804        if ($success === false) {
805            throw new Exception('QRCode: function image'.$format.' failed.');
806        }
807
808        $content = ob_get_clean();
809
810        return $content;
811    }
812
813    /**
814     * Create the image.
815     *
816     * @throws Exception
817     * @throws \OverflowException
818     */
819    public function create()
820    {
821        $image_path = $this->image_path;
822        $path = $this->path;
823
824        $version_ul = 40;
825
826        $qrcode_data_string = $this->text;//Previously from $_GET["d"];
827
828        $qrcode_error_correct = $this->error_correction;//Previously from $_GET["e"];
829        $qrcode_module_size = $this->module_size;//Previously from $_GET["s"];
830        $qrcode_version = $this->version;//Previously from $_GET["v"];
831        $qrcode_image_type = $this->image_type;//Previously from $_GET["t"];
832
833        $qrcode_structureappend_n = $this->structure_append_n;//Previously from $_GET["n"];
834        $qrcode_structureappend_m = $this->structure_append_m;//Previously from $_GET["m"];
835        $qrcode_structureappend_parity = $this->structure_append_parity;//Previously from $_GET["p"];
836        $qrcode_structureappend_originaldata = $this->structure_append_original_data;//Previously from $_GET["o"];
837
838        if ($qrcode_module_size > 0) {
839        } else {
840            if ($qrcode_image_type == 'jpeg') {
841                $qrcode_module_size = 8;
842            } else {
843                $qrcode_module_size = 4;
844            }
845        }
846        $data_length = strlen($qrcode_data_string);
847        if ($data_length <= 0) {
848            throw new Exception('QRCode: data does not exist.');
849        }
850        $data_counter = 0;
851        if ($qrcode_structureappend_n > 1
852         && $qrcode_structureappend_n <= 16
853         && $qrcode_structureappend_m > 0
854         && $qrcode_structureappend_m <= 16) {
855            $data_value[0] = 3;
856            $data_bits[0] = 4;
857
858            $data_value[1] = $qrcode_structureappend_m - 1;
859            $data_bits[1] = 4;
860
861            $data_value[2] = $qrcode_structureappend_n - 1;
862            $data_bits[2] = 4;
863
864            $originaldata_length = strlen($qrcode_structureappend_originaldata);
865            if ($originaldata_length > 1) {
866                $qrcode_structureappend_parity = 0;
867                $i = 0;
868                while ($i < $originaldata_length) {
869                    $qrcode_structureappend_parity = ($qrcode_structureappend_parity ^ ord(substr($qrcode_structureappend_originaldata, $i, 1)));
870                    ++$i;
871                }
872            }
873
874            $data_value[3] = $qrcode_structureappend_parity;
875            $data_bits[3] = 8;
876
877            $data_counter = 4;
878        }
879
880        $data_bits[$data_counter] = 4;
881
882        /*  --- determine encode mode */
883
884        if (preg_match('/[^0-9]/', $qrcode_data_string) != 0) {
885            if (preg_match("/[^0-9A-Z \$\*\%\+\.\/\:\-]/", $qrcode_data_string) != 0) {
886                /*  --- 8bit byte mode */
887
888                $codeword_num_plus = array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
889        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
890        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, );
891
892                $data_value[$data_counter] = 4;
893                ++$data_counter;
894                $data_value[$data_counter] = $data_length;
895                $data_bits[$data_counter] = 8;   /* #version 1-9 */
896                $codeword_num_counter_value = $data_counter;
897
898                ++$data_counter;
899                $i = 0;
900                while ($i < $data_length) {
901                    $data_value[$data_counter] = ord(substr($qrcode_data_string, $i, 1));
902                    $data_bits[$data_counter] = 8;
903                    ++$data_counter;
904                    ++$i;
905                }
906            } else {
907                /* ---- alphanumeric mode */
908
909                $codeword_num_plus = array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
910        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
911        4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, );
912
913                $data_value[$data_counter] = 2;
914                ++$data_counter;
915                $data_value[$data_counter] = $data_length;
916                $data_bits[$data_counter] = 9;  /* #version 1-9 */
917                $codeword_num_counter_value = $data_counter;
918
919                $alphanumeric_character_hash = array('0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4,
920        '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14,
921        'F' => 15, 'G' => 16, 'H' => 17, 'I' => 18, 'J' => 19, 'K' => 20, 'L' => 21, 'M' => 22, 'N' => 23,
922        'O' => 24, 'P' => 25, 'Q' => 26, 'R' => 27, 'S' => 28, 'T' => 29, 'U' => 30, 'V' => 31,
923        'W' => 32, 'X' => 33, 'Y' => 34, 'Z' => 35, ' ' => 36, '$' => 37, '%' => 38, '*' => 39,
924        '+' => 40, '-' => 41, '.' => 42, '/' => 43, ':' => 44, );
925
926                $i = 0;
927                ++$data_counter;
928                while ($i < $data_length) {
929                    if (($i % 2) == 0) {
930                        $data_value[$data_counter] = $alphanumeric_character_hash[substr($qrcode_data_string, $i, 1)];
931                        $data_bits[$data_counter] = 6;
932                    } else {
933                        $data_value[$data_counter] = $data_value[$data_counter] * 45 + $alphanumeric_character_hash[substr($qrcode_data_string, $i, 1)];
934                        $data_bits[$data_counter] = 11;
935                        ++$data_counter;
936                    }
937                    ++$i;
938                }
939            }
940        } else {
941            /* ---- numeric mode */
942
943            $codeword_num_plus = array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
944        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
945        4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, );
946
947            $data_value[$data_counter] = 1;
948            ++$data_counter;
949            $data_value[$data_counter] = $data_length;
950            $data_bits[$data_counter] = 10;   /* #version 1-9 */
951            $codeword_num_counter_value = $data_counter;
952
953            $i = 0;
954            ++$data_counter;
955            while ($i < $data_length) {
956                if (($i % 3) == 0) {
957                    $data_value[$data_counter] = substr($qrcode_data_string, $i, 1);
958                    $data_bits[$data_counter] = 4;
959                } else {
960                    $data_value[$data_counter] = $data_value[$data_counter] * 10 + substr($qrcode_data_string, $i, 1);
961                    if (($i % 3) == 1) {
962                        $data_bits[$data_counter] = 7;
963                    } else {
964                        $data_bits[$data_counter] = 10;
965                        ++$data_counter;
966                    }
967                }
968                ++$i;
969            }
970        }
971        if (array_key_exists($data_counter, $data_bits) && $data_bits[$data_counter] > 0) {
972            ++$data_counter;
973        }
974        $i = 0;
975        $total_data_bits = 0;
976        while ($i < $data_counter) {
977            $total_data_bits += $data_bits[$i];
978            ++$i;
979        }
980
981        $ecc_character_hash = array('L' => '1',
982        'l' => '1',
983        'M' => '0',
984        'm' => '0',
985        'Q' => '3',
986        'q' => '3',
987        'H' => '2',
988        'h' => '2', );
989
990        if (!is_numeric($qrcode_error_correct)) {
991            $ec = @$ecc_character_hash[$qrcode_error_correct];
992        } else {
993            $ec = $qrcode_error_correct;
994        }
995
996        if (!$ec) {
997            $ec = 0;
998        }
999
1000        $max_data_bits = 0;
1001
1002        $max_data_bits_array = array(
1003        0, 128, 224, 352, 512, 688, 864, 992, 1232, 1456, 1728,
1004        2032, 2320, 2672, 2920, 3320, 3624, 4056, 4504, 5016, 5352,
1005        5712, 6256, 6880, 7312, 8000, 8496, 9024, 9544, 10136, 10984,
1006        11640, 12328, 13048, 13800, 14496, 15312, 15936, 16816, 17728, 18672,
1007
1008        152, 272, 440, 640, 864, 1088, 1248, 1552, 1856, 2192,
1009        2592, 2960, 3424, 3688, 4184, 4712, 5176, 5768, 6360, 6888,
1010        7456, 8048, 8752, 9392, 10208, 10960, 11744, 12248, 13048, 13880,
1011        14744, 15640, 16568, 17528, 18448, 19472, 20528, 21616, 22496, 23648,
1012
1013        72, 128, 208, 288, 368, 480, 528, 688, 800, 976,
1014        1120, 1264, 1440, 1576, 1784, 2024, 2264, 2504, 2728, 3080,
1015        3248, 3536, 3712, 4112, 4304, 4768, 5024, 5288, 5608, 5960,
1016        6344, 6760, 7208, 7688, 7888, 8432, 8768, 9136, 9776, 10208,
1017
1018        104, 176, 272, 384, 496, 608, 704, 880, 1056, 1232,
1019        1440, 1648, 1952, 2088, 2360, 2600, 2936, 3176, 3560, 3880,
1020        4096, 4544, 4912, 5312, 5744, 6032, 6464, 6968, 7288, 7880,
1021        8264, 8920, 9368, 9848, 10288, 10832, 11408, 12016, 12656, 13328,
1022        );
1023        if (!is_numeric($qrcode_version)) {
1024            $qrcode_version = 0;
1025        }
1026        if (!$qrcode_version) {
1027            /* #--- auto version select */
1028            $i = 1 + 40 * $ec;
1029            $j = $i + 39;
1030            $qrcode_version = 1;
1031            while ($i <= $j) {
1032                if (($max_data_bits_array[$i]) >= $total_data_bits + $codeword_num_plus[$qrcode_version]) {
1033                    $max_data_bits = $max_data_bits_array[$i];
1034                    break;
1035                }
1036                ++$i;
1037                ++$qrcode_version;
1038            }
1039        } else {
1040            $max_data_bits = $max_data_bits_array[$qrcode_version + 40 * $ec];
1041        }
1042        if ($qrcode_version > $version_ul) {
1043            throw new Exception('QRCode : version too large');
1044        }
1045
1046        $total_data_bits += $codeword_num_plus[$qrcode_version];
1047        $data_bits[$codeword_num_counter_value] += $codeword_num_plus[$qrcode_version];
1048
1049        $max_codewords_array = array(0, 26, 44, 70, 100, 134, 172, 196, 242,
1050        292, 346, 404, 466, 532, 581, 655, 733, 815, 901, 991, 1085, 1156,
1051        1258, 1364, 1474, 1588, 1706, 1828, 1921, 2051, 2185, 2323, 2465,
1052        2611, 2761, 2876, 3034, 3196, 3362, 3532, 3706, );
1053
1054        $max_codewords = $max_codewords_array[$qrcode_version];
1055        $max_modules_1side = 17 + ($qrcode_version << 2);
1056
1057        $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,
1058        4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, );
1059
1060        /* ---- read version ECC data file */
1061
1062        $byte_num = $matrix_remain_bit[$qrcode_version] + ($max_codewords << 3);
1063        $filename = $path.'/qrv'.$qrcode_version.'_'.$ec.'.dat';
1064        $fp1 = fopen($filename, 'rb');
1065        $matx = fread($fp1, $byte_num);
1066        $maty = fread($fp1, $byte_num);
1067        $masks = fread($fp1, $byte_num);
1068        $fi_x = fread($fp1, 15);
1069        $fi_y = fread($fp1, 15);
1070        $rs_ecc_codewords = ord(fread($fp1, 1));
1071        $rso = fread($fp1, 128);
1072        fclose($fp1);
1073
1074        $matrix_x_array = unpack('C*', $matx);
1075        $matrix_y_array = unpack('C*', $maty);
1076        $mask_array = unpack('C*', $masks);
1077
1078        $rs_block_order = unpack('C*', $rso);
1079
1080        $format_information_x2 = unpack('C*', $fi_x);
1081        $format_information_y2 = unpack('C*', $fi_y);
1082
1083        $format_information_x1 = array(0, 1, 2, 3, 4, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8);
1084        $format_information_y1 = array(8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 4, 3, 2, 1, 0);
1085
1086        $max_data_codewords = ($max_data_bits >> 3);
1087
1088        $filename = $path.'/rsc'.$rs_ecc_codewords.'.dat';
1089        $fp0 = fopen($filename, 'rb');
1090        $i = 0;
1091        $rs_cal_table_array = array();
1092        while ($i < 256) {
1093            $rs_cal_table_array[$i] = fread($fp0, $rs_ecc_codewords);
1094            ++$i;
1095        }
1096        fclose($fp0);
1097
1098        /*  --- set terminator */
1099
1100        if ($total_data_bits <= $max_data_bits - 4) {
1101            $data_value[$data_counter] = 0;
1102            $data_bits[$data_counter] = 4;
1103        } else {
1104            if ($total_data_bits < $max_data_bits) {
1105                $data_value[$data_counter] = 0;
1106                $data_bits[$data_counter] = $max_data_bits - $total_data_bits;
1107            } else {
1108                if ($total_data_bits > $max_data_bits) {
1109                    throw new \OverflowException('QRCode: overflow error');
1110                }
1111            }
1112        }
1113
1114        /* ----divide data by 8bit */
1115
1116        $i = 0;
1117        $codewords_counter = 0;
1118        $codewords[0] = 0;
1119        $remaining_bits = 8;
1120
1121        while ($i <= $data_counter) {
1122            $buffer = @$data_value[$i];
1123            $buffer_bits = @$data_bits[$i];
1124
1125            $flag = 1;
1126            while ($flag) {
1127                if ($remaining_bits > $buffer_bits) {
1128                    $codewords[$codewords_counter] = ((@$codewords[$codewords_counter] << $buffer_bits) | $buffer);
1129                    $remaining_bits -= $buffer_bits;
1130                    $flag = 0;
1131                } else {
1132                    $buffer_bits -= $remaining_bits;
1133                    $codewords[$codewords_counter] = (($codewords[$codewords_counter] << $remaining_bits) | ($buffer >> $buffer_bits));
1134
1135                    if ($buffer_bits == 0) {
1136                        $flag = 0;
1137                    } else {
1138                        $buffer = ($buffer & ((1 << $buffer_bits) - 1));
1139                        $flag = 1;
1140                    }
1141
1142                    ++$codewords_counter;
1143                    if ($codewords_counter < $max_data_codewords - 1) {
1144                        $codewords[$codewords_counter] = 0;
1145                    }
1146                    $remaining_bits = 8;
1147                }
1148            }
1149            ++$i;
1150        }
1151        if ($remaining_bits != 8) {
1152            $codewords[$codewords_counter] = $codewords[$codewords_counter] << $remaining_bits;
1153        } else {
1154            --$codewords_counter;
1155        }
1156
1157        /* ----  set padding character */
1158
1159        if ($codewords_counter < $max_data_codewords - 1) {
1160            $flag = 1;
1161            while ($codewords_counter < $max_data_codewords - 1) {
1162                ++$codewords_counter;
1163                if ($flag == 1) {
1164                    $codewords[$codewords_counter] = 236;
1165                } else {
1166                    $codewords[$codewords_counter] = 17;
1167                }
1168                $flag = $flag * (-1);
1169            }
1170        }
1171
1172        /* ---- RS-ECC prepare */
1173
1174        $i = 0;
1175        $j = 0;
1176        $rs_block_number = 0;
1177        $rs_temp[0] = '';
1178
1179        while ($i < $max_data_codewords) {
1180            $rs_temp[$rs_block_number] .= chr($codewords[$i]);
1181            ++$j;
1182
1183            if ($j >= $rs_block_order[$rs_block_number + 1] - $rs_ecc_codewords) {
1184                $j = 0;
1185                ++$rs_block_number;
1186                $rs_temp[$rs_block_number] = '';
1187            }
1188            ++$i;
1189        }
1190
1191        /*
1192        #
1193        # RS-ECC main
1194        #
1195        */
1196
1197        $rs_block_number = 0;
1198        $rs_block_order_num = count($rs_block_order);
1199
1200        while ($rs_block_number < $rs_block_order_num) {
1201            $rs_codewords = $rs_block_order[$rs_block_number + 1];
1202            $rs_data_codewords = $rs_codewords - $rs_ecc_codewords;
1203
1204            $rstemp = $rs_temp[$rs_block_number].str_repeat(chr(0), $rs_ecc_codewords);
1205            $padding_data = str_repeat(chr(0), $rs_data_codewords);
1206
1207            $j = $rs_data_codewords;
1208            while ($j > 0) {
1209                $first = ord(substr($rstemp, 0, 1));
1210
1211                if ($first) {
1212                    $left_chr = substr($rstemp, 1);
1213                    $cal = $rs_cal_table_array[$first].$padding_data;
1214                    $rstemp = $left_chr ^ $cal;
1215                } else {
1216                    $rstemp = substr($rstemp, 1);
1217                }
1218
1219                --$j;
1220            }
1221
1222            $codewords = array_merge($codewords, unpack('C*', $rstemp));
1223
1224            ++$rs_block_number;
1225        }
1226
1227        /* ---- flash matrix */
1228        $matrix_content = array();
1229        $i = 0;
1230        while ($i < $max_modules_1side) {
1231            $j = 0;
1232            while ($j < $max_modules_1side) {
1233                $matrix_content[$j][$i] = 0;
1234                ++$j;
1235            }
1236            ++$i;
1237        }
1238
1239        /* --- attach data */
1240
1241        $i = 0;
1242        while ($i < $max_codewords) {
1243            $codeword_i = $codewords[$i];
1244            $j = 8;
1245            while ($j >= 1) {
1246                $codeword_bits_number = ($i << 3) +  $j;
1247                $matrix_content[ $matrix_x_array[$codeword_bits_number] ][ $matrix_y_array[$codeword_bits_number] ] = ((255 * ($codeword_i & 1)) ^ $mask_array[$codeword_bits_number]);
1248                $codeword_i = $codeword_i >> 1;
1249                --$j;
1250            }
1251            ++$i;
1252        }
1253
1254        $matrix_remain = $matrix_remain_bit[$qrcode_version];
1255        while ($matrix_remain) {
1256            $remain_bit_temp = $matrix_remain + ($max_codewords << 3);
1257            $matrix_content[ $matrix_x_array[$remain_bit_temp] ][ $matrix_y_array[$remain_bit_temp] ] = (255 ^ $mask_array[$remain_bit_temp]);
1258            --$matrix_remain;
1259        }
1260
1261        #--- mask select
1262
1263        $min_demerit_score = 0;
1264        $hor_master = '';
1265        $ver_master = '';
1266        $k = 0;
1267        while ($k < $max_modules_1side) {
1268            $l = 0;
1269            while ($l < $max_modules_1side) {
1270                $hor_master = $hor_master.chr($matrix_content[$l][$k]);
1271                $ver_master = $ver_master.chr($matrix_content[$k][$l]);
1272                ++$l;
1273            }
1274            ++$k;
1275        }
1276        $i = 0;
1277        $all_matrix = $max_modules_1side * $max_modules_1side;
1278        $mask_number = 0;
1279        while ($i < 8) {
1280            $demerit_n1 = 0;
1281            $ptn_temp = array();
1282            $bit = 1 << $i;
1283            $bit_r = (~$bit) & 255;
1284            $bit_mask = str_repeat(chr($bit), $all_matrix);
1285            $hor = $hor_master & $bit_mask;
1286            $ver = $ver_master & $bit_mask;
1287
1288            $ver_shift1 = $ver.str_repeat(chr(170), $max_modules_1side);
1289            $ver_shift2 = str_repeat(chr(170), $max_modules_1side).$ver;
1290            $ver_shift1_0 = $ver.str_repeat(chr(0), $max_modules_1side);
1291            $ver_shift2_0 = str_repeat(chr(0), $max_modules_1side).$ver;
1292            $ver_or = chunk_split(~($ver_shift1 | $ver_shift2), $max_modules_1side, chr(170));
1293            $ver_and = chunk_split(~($ver_shift1_0 & $ver_shift2_0), $max_modules_1side, chr(170));
1294
1295            $hor = chunk_split(~$hor, $max_modules_1side, chr(170));
1296            $ver = chunk_split(~$ver, $max_modules_1side, chr(170));
1297            $hor = $hor.chr(170).$ver;
1298
1299            $n1_search = '/'.str_repeat(chr(255), 5).'+|'.str_repeat(chr($bit_r), 5).'+/';
1300            $n3_search = chr($bit_r).chr(255).chr($bit_r).chr($bit_r).chr($bit_r).chr(255).chr($bit_r);
1301
1302            $demerit_n3 = substr_count($hor, $n3_search) * 40;
1303            $demerit_n4 = floor(abs(((100 * (substr_count($ver, chr($bit_r)) / ($byte_num))) - 50) / 5)) * 10;
1304
1305            $n2_search1 = '/'.chr($bit_r).chr($bit_r).'+/';
1306            $n2_search2 = '/'.chr(255).chr(255).'+/';
1307            $demerit_n2 = 0;
1308            preg_match_all($n2_search1, $ver_and, $ptn_temp);
1309            foreach ($ptn_temp[0] as $str_temp) {
1310                $demerit_n2 += (strlen($str_temp) - 1);
1311            }
1312            $ptn_temp = array();
1313            preg_match_all($n2_search2, $ver_or, $ptn_temp);
1314            foreach ($ptn_temp[0] as $str_temp) {
1315                $demerit_n2 += (strlen($str_temp) - 1);
1316            }
1317            $demerit_n2 *= 3;
1318
1319            $ptn_temp = array();
1320
1321            preg_match_all($n1_search, $hor, $ptn_temp);
1322            foreach ($ptn_temp[0] as $str_temp) {
1323                $demerit_n1 += (strlen($str_temp) - 2);
1324            }
1325
1326            $demerit_score = $demerit_n1 + $demerit_n2 + $demerit_n3 + $demerit_n4;
1327
1328            if ($demerit_score <= $min_demerit_score || $i == 0) {
1329                $mask_number = $i;
1330                $min_demerit_score = $demerit_score;
1331            }
1332
1333            ++$i;
1334        }
1335
1336        $mask_content = 1 << $mask_number;
1337
1338        # --- format information
1339
1340        $format_information_value = (($ec << 3) | $mask_number);
1341        $format_information_array = array('101010000010010', '101000100100101',
1342        '101111001111100', '101101101001011', '100010111111001', '100000011001110',
1343        '100111110010111', '100101010100000', '111011111000100', '111001011110011',
1344        '111110110101010', '111100010011101', '110011000101111', '110001100011000',
1345        '110110001000001', '110100101110110', '001011010001001', '001001110111110',
1346        '001110011100111', '001100111010000', '000011101100010', '000001001010101',
1347        '000110100001100', '000100000111011', '011010101011111', '011000001101000',
1348        '011111100110001', '011101000000110', '010010010110100', '010000110000011',
1349        '010111011011010', '010101111101101', );
1350        $i = 0;
1351        while ($i < 15) {
1352            $content = substr($format_information_array[$format_information_value], $i, 1);
1353
1354            $matrix_content[$format_information_x1[$i]][$format_information_y1[$i]] = $content * 255;
1355            $matrix_content[$format_information_x2[$i + 1]][$format_information_y2[$i + 1]] = $content * 255;
1356            ++$i;
1357        }
1358
1359        $mib = $max_modules_1side + 8;
1360
1361        if ($this->size == 0) {
1362            $this->size = $mib * $qrcode_module_size;
1363            if ($this->size > 1480) {
1364                throw new Exception('QRCode: image size too large');
1365            }
1366        }
1367
1368        $image_width = $this->size + $this->padding * 2;
1369        $image_height = $this->size + $this->padding * 2;
1370
1371        if (!empty($this->label)) {
1372            if (!function_exists('imagettfbbox')) {
1373                throw new Exception('QRCode: missing function "imagettfbbox". Did you install the FreeType library?');
1374            }
1375            $font_box = imagettfbbox($this->label_font_size, 0, $this->label_font_path, $this->label);
1376            $label_width = (int) $font_box[2] - (int) $font_box[0];
1377            $label_height = (int) $font_box[0] - (int) $font_box[7];
1378
1379            if ($this->label_valign == self::LABEL_VALIGN_MIDDLE) {
1380                $image_height += $label_height + $this->padding;
1381            } else {
1382                $image_height += $label_height;
1383            }
1384        }
1385
1386        $output_image = imagecreate($image_width, $image_height);
1387        imagecolorallocate($output_image, 255, 255, 255);
1388
1389        $image_path = $image_path.'/qrv'.$qrcode_version.'.png';
1390
1391        $base_image = imagecreatefrompng($image_path);
1392        $code_size = $this->size;
1393        $module_size = function ($size = 1) use ($code_size, $base_image) {
1394            return round($code_size / imagesx($base_image) * $size);
1395        };
1396
1397        $col[1] = imagecolorallocate($base_image, 0, 0, 0);
1398        $col[0] = imagecolorallocate($base_image, 255, 255, 255);
1399
1400        $i = 4;
1401        $mxe = 4 + $max_modules_1side;
1402        $ii = 0;
1403        while ($i < $mxe) {
1404            $j = 4;
1405            $jj = 0;
1406            while ($j < $mxe) {
1407                if ($matrix_content[$ii][$jj] & $mask_content) {
1408                    imagesetpixel($base_image, $i, $j, $col[1]);
1409                }
1410                ++$j;
1411                ++$jj;
1412            }
1413            ++$i;
1414            ++$ii;
1415        }
1416
1417        if ($this->draw_quiet_zone == true) {
1418            imagecopyresampled($output_image, $base_image, $this->padding, $this->padding, 0, 0, $this->size, $this->size, $mib, $mib);
1419        } else {
1420            imagecopyresampled($output_image, $base_image, $this->padding, $this->padding, 4, 4, $this->size, $this->size, $mib - 8, $mib - 8);
1421        }
1422
1423        if ($this->draw_border == true) {
1424            $border_width = $this->padding;
1425            $border_height = $this->size + $this->padding - 1;
1426            $border_color = imagecolorallocate($output_image, 0, 0, 0);
1427            imagerectangle($output_image, $border_width, $border_width, $border_height, $border_height, $border_color);
1428        }
1429
1430        if (!empty($this->label)) {
1431            // Label horizontal alignment
1432            switch ($this->label_halign) {
1433                case self::LABEL_HALIGN_LEFT:
1434                    $font_x = 0;
1435                    break;
1436
1437                case self::LABEL_HALIGN_LEFT_BORDER:
1438                    $font_x = $this->padding;
1439                    break;
1440
1441                case self::LABEL_HALIGN_LEFT_CODE:
1442                    if ($this->draw_quiet_zone == true) {
1443                        $font_x = $this->padding + $module_size(4);
1444                    } else {
1445                        $font_x = $this->padding;
1446                    }
1447                    break;
1448
1449                case self::LABEL_HALIGN_RIGHT:
1450                    $font_x = $this->size + ($this->padding * 2) - $label_width;
1451                    break;
1452
1453                case self::LABEL_HALIGN_RIGHT_BORDER:
1454                    $font_x = $this->size + $this->padding - $label_width;
1455                    break;
1456
1457                case self::LABEL_HALIGN_RIGHT_CODE:
1458                    if ($this->draw_quiet_zone == true) {
1459                        $font_x = $this->size + $this->padding - $label_width - $module_size(4);
1460                    } else {
1461                        $font_x = $this->size + $this->padding - $label_width;
1462                    }
1463                    break;
1464
1465                default:
1466                    $font_x = floor($image_width - $label_width) / 2;
1467            }
1468
1469            // Label vertical alignment
1470            switch ($this->label_valign) {
1471                case self::LABEL_VALIGN_TOP_NO_BORDER:
1472                    $font_y = $image_height - $this->padding - 1;
1473                    break;
1474
1475                case self::LABEL_VALIGN_BOTTOM:
1476                    $font_y = $image_height;
1477                    break;
1478
1479                default:
1480                    $font_y = $image_height - $this->padding;
1481            }
1482
1483            $label_bg_x1 = $font_x - $module_size(2);
1484            $label_bg_y1 = $font_y - $label_height;
1485            $label_bg_x2 = $font_x + $label_width + $module_size(2);
1486            $label_bg_y2 = $font_y;
1487
1488            $color = imagecolorallocate($output_image, 0, 0, 0);
1489            $label_bg_color = imagecolorallocate($output_image, 255, 255, 255);
1490
1491            imagefilledrectangle($output_image, $label_bg_x1, $label_bg_y1, $label_bg_x2, $label_bg_y2, $label_bg_color);
1492            imagettftext($output_image, $this->label_font_size, 0, $font_x, $font_y, $color, $this->label_font_path, $this->label);
1493        }
1494
1495        $imagecolorset_function = new ReflectionFunction('imagecolorset');
1496        $allow_alpha = $imagecolorset_function->getNumberOfParameters() == 6;
1497
1498        if ($this->color_background != null) {
1499            $index = imagecolorclosest($output_image, 255, 255, 255);
1500            if ($allow_alpha) {
1501                imagecolorset($output_image, $index, $this->color_background['r'], $this->color_background['g'], $this->color_background['b'], $this->color_background['a']);
1502            } else {
1503                imagecolorset($output_image, $index, $this->color_background['r'], $this->color_background['g'], $this->color_background['b']);
1504            }
1505        }
1506
1507        if ($this->color_foreground != null) {
1508            $index = imagecolorclosest($output_image, 0, 0, 0);
1509            if ($allow_alpha) {
1510                imagecolorset($output_image, $index, $this->color_foreground['r'], $this->color_foreground['g'], $this->color_foreground['b'], $this->color_foreground['a']);
1511            } else {
1512                imagecolorset($output_image, $index, $this->color_foreground['r'], $this->color_foreground['g'], $this->color_foreground['b']);
1513            }
1514        }
1515
1516        $this->image = $output_image;
1517    }
1518}
1519