<?php

/*

 * PHP QR Code encoder

 *

 * Input encoding class

 *

 * Based on libqrencode C library distributed under LGPL 2.1

 * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>

 *

 * PHP QR Code is distributed under LGPL 3

 * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>

 *

 * This library is free software; you can redistribute it and/or

 * modify it under the terms of the GNU Lesser General Public

 * License as published by the Free Software Foundation; either

 * version 3 of the License, or any later version.

 *

 * This library is distributed in the hope that it will be useful,

 * but WITHOUT ANY WARRANTY; without even the implied warranty of

 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU

 * Lesser General Public License for more details.

 *

 * You should have received a copy of the GNU Lesser General Public

 * License along with this library; if not, write to the Free Software

 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

 */

 

    define('STRUCTURE_HEADER_BITS',  20);

    define('MAX_STRUCTURED_SYMBOLS', 16);



    class QRinputItem {

    

        public $mode;

        public $size;

        public $data;

        public $bstream;



        public function __construct($mode, $size, $data, $bstream = null) 

        {

            $setData = array_slice($data, 0, $size);

            

            if (count($setData) < $size) {

                $setData = array_merge($setData, array_fill(0,$size-count($setData),0));

            }

        

            if(!QRinput::check($mode, $size, $setData)) {

                throw new Exception('Error m:'.$mode.',s:'.$size.',d:'.join(',',$setData));

                return null;

            }

            

            $this->mode = $mode;

            $this->size = $size;

            $this->data = $setData;

            $this->bstream = $bstream;

        }

        

        //----------------------------------------------------------------------

        public function encodeModeNum($version)

        {

            try {

            

                $words = (int)($this->size / 3);

                $bs = new QRbitstream();

                

                $val = 0x1;

                $bs->appendNum(4, $val);

                $bs->appendNum(QRspec::lengthIndicator(QR_MODE_NUM, $version), $this->size);



                for($i=0; $i<$words; $i++) {

                    $val  = (ord($this->data[$i*3  ]) - ord('0')) * 100;

                    $val += (ord($this->data[$i*3+1]) - ord('0')) * 10;

                    $val += (ord($this->data[$i*3+2]) - ord('0'));

                    $bs->appendNum(10, $val);

                }



                if($this->size - $words * 3 == 1) {

                    $val = ord($this->data[$words*3]) - ord('0');

                    $bs->appendNum(4, $val);

                } else if($this->size - $words * 3 == 2) {

                    $val  = (ord($this->data[$words*3  ]) - ord('0')) * 10;

                    $val += (ord($this->data[$words*3+1]) - ord('0'));

                    $bs->appendNum(7, $val);

                }



                $this->bstream = $bs;

                return 0;

                

            } catch (Exception $e) {

                return -1;

            }

        }

        

        //----------------------------------------------------------------------

        public function encodeModeAn($version)

        {

            try {

                $words = (int)($this->size / 2);

                $bs = new QRbitstream();

                

                $bs->appendNum(4, 0x02);

                $bs->appendNum(QRspec::lengthIndicator(QR_MODE_AN, $version), $this->size);



                for($i=0; $i<$words; $i++) {

                    $val  = (int)QRinput::lookAnTable(ord($this->data[$i*2  ])) * 45;

                    $val += (int)QRinput::lookAnTable(ord($this->data[$i*2+1]));



                    $bs->appendNum(11, $val);

                }



                if($this->size & 1) {

                    $val = QRinput::lookAnTable(ord($this->data[$words * 2]));

                    $bs->appendNum(6, $val);

                }

        

                $this->bstream = $bs;

                return 0;

            

            } catch (Exception $e) {

                return -1;

            }

        }

        

        //----------------------------------------------------------------------

        public function encodeMode8($version)

        {

            try {

                $bs = new QRbitstream();



                $bs->appendNum(4, 0x4);

                $bs->appendNum(QRspec::lengthIndicator(QR_MODE_8, $version), $this->size);



                for($i=0; $i<$this->size; $i++) {

                    $bs->appendNum(8, ord($this->data[$i]));

                }



                $this->bstream = $bs;

                return 0;

            

            } catch (Exception $e) {

                return -1;

            }

        }

        

        //----------------------------------------------------------------------

        public function encodeModeKanji($version)

        {

            try {



                $bs = new QRbitrtream();

                

                $bs->appendNum(4, 0x8);

                $bs->appendNum(QRspec::lengthIndicator(QR_MODE_KANJI, $version), (int)($this->size / 2));



                for($i=0; $i<$this->size; $i+=2) {

                    $val = (ord($this->data[$i]) << 8) | ord($this->data[$i+1]);

                    if($val <= 0x9ffc) {

                        $val -= 0x8140;

                    } else {

                        $val -= 0xc140;

                    }

                    

                    $h = ($val >> 8) * 0xc0;

                    $val = ($val & 0xff) + $h;



                    $bs->appendNum(13, $val);

                }



                $this->bstream = $bs;

                return 0;

            

            } catch (Exception $e) {

                return -1;

            }

        }



        //----------------------------------------------------------------------

        public function encodeModeStructure()

        {

            try {

                $bs =  new QRbitstream();

                

                $bs->appendNum(4, 0x03);

                $bs->appendNum(4, ord($this->data[1]) - 1);

                $bs->appendNum(4, ord($this->data[0]) - 1);

                $bs->appendNum(8, ord($this->data[2]));



                $this->bstream = $bs;

                return 0;

            

            } catch (Exception $e) {

                return -1;

            }

        }

        

        //----------------------------------------------------------------------

        public function estimateBitStreamSizeOfEntry($version)

        {

            $bits = 0;



            if($version == 0) 

                $version = 1;



            switch($this->mode) {

                case QR_MODE_NUM:        $bits = QRinput::estimateBitsModeNum($this->size);    break;

                case QR_MODE_AN:        $bits = QRinput::estimateBitsModeAn($this->size);    break;

                case QR_MODE_8:            $bits = QRinput::estimateBitsMode8($this->size);    break;

                case QR_MODE_KANJI:        $bits = QRinput::estimateBitsModeKanji($this->size);break;

                case QR_MODE_STRUCTURE:    return STRUCTURE_HEADER_BITS;            

                default:

                    return 0;

            }



            $l = QRspec::lengthIndicator($this->mode, $version);

            $m = 1 << $l;

            $num = (int)(($this->size + $m - 1) / $m);



            $bits += $num * (4 + $l);



            return $bits;

        }

        

        //----------------------------------------------------------------------

        public function encodeBitStream($version)

        {

            try {

            

                unset($this->bstream);

                $words = QRspec::maximumWords($this->mode, $version);

                

                if($this->size > $words) {

                

                    $st1 = new QRinputItem($this->mode, $words, $this->data);

                    $st2 = new QRinputItem($this->mode, $this->size - $words, array_slice($this->data, $words));



                    $st1->encodeBitStream($version);

                    $st2->encodeBitStream($version);

                    

                    $this->bstream = new QRbitstream();

                    $this->bstream->append($st1->bstream);

                    $this->bstream->append($st2->bstream);

                    

                    unset($st1);

                    unset($st2);

                    

                } else {

                    

                    $ret = 0;

                    

                    switch($this->mode) {

                        case QR_MODE_NUM:        $ret = $this->encodeModeNum($version);    break;

                        case QR_MODE_AN:        $ret = $this->encodeModeAn($version);    break;

                        case QR_MODE_8:            $ret = $this->encodeMode8($version);    break;

                        case QR_MODE_KANJI:        $ret = $this->encodeModeKanji($version);break;

                        case QR_MODE_STRUCTURE:    $ret = $this->encodeModeStructure();    break;

                        

                        default:

                            break;

                    }

                    

                    if($ret < 0)

                        return -1;

                }



                return $this->bstream->size();

            

            } catch (Exception $e) {

                return -1;

            }

        }

    };

    

    //##########################################################################



    class QRinput {



        public $items;

        

        private $version;

        private $level;

        

        //----------------------------------------------------------------------

        public function __construct($version = 0, $level = QR_ECLEVEL_L)

        {

            if ($version < 0 || $version > QRSPEC_VERSION_MAX || $level > QR_ECLEVEL_H) {

                throw new Exception('Invalid version no');

                return NULL;

            }

            

            $this->version = $version;

            $this->level = $level;

        }

        

        //----------------------------------------------------------------------

        public function getVersion()

        {

            return $this->version;

        }

        

        //----------------------------------------------------------------------

        public function setVersion($version)

        {

            if($version < 0 || $version > QRSPEC_VERSION_MAX) {

                throw new Exception('Invalid version no');

                return -1;

            }



            $this->version = $version;



            return 0;

        }

        

        //----------------------------------------------------------------------

        public function getErrorCorrectionLevel()

        {

            return $this->level;

        }



        //----------------------------------------------------------------------

        public function setErrorCorrectionLevel($level)

        {

            if($level > QR_ECLEVEL_H) {

                throw new Exception('Invalid ECLEVEL');

                return -1;

            }



            $this->level = $level;



            return 0;

        }

        

        //----------------------------------------------------------------------

        public function appendEntry(QRinputItem $entry)

        {

            $this->items[] = $entry;

        }

        

        //----------------------------------------------------------------------

        public function append($mode, $size, $data)

        {

            try {

                $entry = new QRinputItem($mode, $size, $data);

                $this->items[] = $entry;

                return 0;

            } catch (Exception $e) {

                return -1;

            }

        }

        

        //----------------------------------------------------------------------

        

        public function insertStructuredAppendHeader($size, $index, $parity)

        {

            if( $size > MAX_STRUCTURED_SYMBOLS ) {

                throw new Exception('insertStructuredAppendHeader wrong size');

            }

            

            if( $index <= 0 || $index > MAX_STRUCTURED_SYMBOLS ) {

                throw new Exception('insertStructuredAppendHeader wrong index');

            }



            $buf = array($size, $index, $parity);

            

            try {

                $entry = new QRinputItem(QR_MODE_STRUCTURE, 3, buf);

                array_unshift($this->items, $entry);

                return 0;

            } catch (Exception $e) {

                return -1;

            }

        }



        //----------------------------------------------------------------------

        public function calcParity()

        {

            $parity = 0;

            

            foreach($this->items as $item) {

                if($item->mode != QR_MODE_STRUCTURE) {

                    for($i=$item->size-1; $i>=0; $i--) {

                        $parity ^= $item->data[$i];

                    }

                }

            }



            return $parity;

        }

        

        //----------------------------------------------------------------------

        public static function checkModeNum($size, $data)

        {

            for($i=0; $i<$size; $i++) {

                if((ord($data[$i]) < ord('0')) || (ord($data[$i]) > ord('9'))){

                    return false;

                }

            }



            return true;

        }



        //----------------------------------------------------------------------

        public static function estimateBitsModeNum($size)

        {

            $w = (int)$size / 3;

            $bits = $w * 10;

            

            switch($size - $w * 3) {

                case 1:

                    $bits += 4;

                    break;

                case 2:

                    $bits += 7;

                    break;

                default:

                    break;

            }



            return $bits;

        }

        

        //----------------------------------------------------------------------

        public static $anTable = array(

            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,

            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,

            36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43,

             0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 44, -1, -1, -1, -1, -1,

            -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,

            25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,

            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,

            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1

        );

        

        //----------------------------------------------------------------------

        public static function lookAnTable($c)

        {

            return (($c > 127)?-1:self::$anTable[$c]);

        }

        

        //----------------------------------------------------------------------

        public static function checkModeAn($size, $data)

        {

            for($i=0; $i<$size; $i++) {

                if (self::lookAnTable(ord($data[$i])) == -1) {

                    return false;

                }

            }



            return true;

        }

        

        //----------------------------------------------------------------------

        public static function estimateBitsModeAn($size)

        {

            $w = (int)($size / 2);

            $bits = $w * 11;

            

            if($size & 1) {

                $bits += 6;

            }



            return $bits;

        }

    

        //----------------------------------------------------------------------

        public static function estimateBitsMode8($size)

        {

            return $size * 8;

        }

        

        //----------------------------------------------------------------------

        public function estimateBitsModeKanji($size)

        {

            return (int)(($size / 2) * 13);

        }

        

        //----------------------------------------------------------------------

        public static function checkModeKanji($size, $data)

        {

            if($size & 1)

                return false;



            for($i=0; $i<$size; $i+=2) {

                $val = (ord($data[$i]) << 8) | ord($data[$i+1]);

                if( $val < 0x8140 

                || ($val > 0x9ffc && $val < 0xe040) 

                || $val > 0xebbf) {

                    return false;

                }

            }



            return true;

        }



        /***********************************************************************

         * Validation

         **********************************************************************/



        public static function check($mode, $size, $data)

        {

            if($size <= 0) 

                return false;



            switch($mode) {

                case QR_MODE_NUM:       return self::checkModeNum($size, $data);   break;

                case QR_MODE_AN:        return self::checkModeAn($size, $data);    break;

                case QR_MODE_KANJI:     return self::checkModeKanji($size, $data); break;

                case QR_MODE_8:         return true; break;

                case QR_MODE_STRUCTURE: return true; break;

                

                default:

                    break;

            }



            return false;

        }

        

        

        //----------------------------------------------------------------------

        public function estimateBitStreamSize($version)

        {

            $bits = 0;



            foreach($this->items as $item) {

                $bits += $item->estimateBitStreamSizeOfEntry($version);

            }



            return $bits;

        }

        

        //----------------------------------------------------------------------

        public function estimateVersion()

        {

            $version = 0;

            $prev = 0;

            do {

                $prev = $version;

                $bits = $this->estimateBitStreamSize($prev);

                $version = QRspec::getMinimumVersion((int)(($bits + 7) / 8), $this->level);

                if ($version < 0) {

                    return -1;

                }

            } while ($version > $prev);



            return $version;

        }

        

        //----------------------------------------------------------------------

        public static function lengthOfCode($mode, $version, $bits)

        {

            $payload = $bits - 4 - QRspec::lengthIndicator($mode, $version);

            switch($mode) {

                case QR_MODE_NUM:

                    $chunks = (int)($payload / 10);

                    $remain = $payload - $chunks * 10;

                    $size = $chunks * 3;

                    if($remain >= 7) {

                        $size += 2;

                    } else if($remain >= 4) {

                        $size += 1;

                    }

                    break;

                case QR_MODE_AN:

                    $chunks = (int)($payload / 11);

                    $remain = $payload - $chunks * 11;

                    $size = $chunks * 2;

                    if($remain >= 6) 

                        $size++;

                    break;

                case QR_MODE_8:

                    $size = (int)($payload / 8);

                    break;

                case QR_MODE_KANJI:

                    $size = (int)(($payload / 13) * 2);

                    break;

                case QR_MODE_STRUCTURE:

                    $size = (int)($payload / 8);

                    break;

                default:

                    $size = 0;

                    break;

            }

            

            $maxsize = QRspec::maximumWords($mode, $version);

            if($size < 0) $size = 0;

            if($size > $maxsize) $size = $maxsize;



            return $size;

        }

        

        //----------------------------------------------------------------------

        public function createBitStream()

        {

            $total = 0;



            foreach($this->items as $item) {

                $bits = $item->encodeBitStream($this->version);

                

                if($bits < 0) 

                    return -1;

                    

                $total += $bits;

            }



            return $total;

        }

        

        //----------------------------------------------------------------------

        public function convertData()

        {

            $ver = $this->estimateVersion();

            if($ver > $this->getVersion()) {

                $this->setVersion($ver);

            }



            for(;;) {

                $bits = $this->createBitStream();

                

                if($bits < 0) 

                    return -1;

                    

                $ver = QRspec::getMinimumVersion((int)(($bits + 7) / 8), $this->level);

                if($ver < 0) {

                    throw new Exception('WRONG VERSION');

                    return -1;

                } else if($ver > $this->getVersion()) {

                    $this->setVersion($ver);

                } else {

                    break;

                }

            }



            return 0;

        }

        

        //----------------------------------------------------------------------

        public function appendPaddingBit(&$bstream)

        {

            $bits = $bstream->size();

            $maxwords = QRspec::getDataLength($this->version, $this->level);

            $maxbits = $maxwords * 8;



            if ($maxbits == $bits) {

                return 0;

            }



            if ($maxbits - $bits < 5) {

                return $bstream->appendNum($maxbits - $bits, 0);

            }



            $bits += 4;

            $words = (int)(($bits + 7) / 8);



            $padding = new QRbitstream();

            $ret = $padding->appendNum($words * 8 - $bits + 4, 0);

            

            if($ret < 0) 

                return $ret;



            $padlen = $maxwords - $words;

            

            if($padlen > 0) {

                

                $padbuf = array();

                for($i=0; $i<$padlen; $i++) {

                    $padbuf[$i] = ($i&1)?0x11:0xec;

                }

                

                $ret = $padding->appendBytes($padlen, $padbuf);

                

                if($ret < 0)

                    return $ret;

                

            }



            $ret = $bstream->append($padding);

            

            return $ret;

        }



        //----------------------------------------------------------------------

        public function mergeBitStream()

        {

            if($this->convertData() < 0) {

                return null;

            }



            $bstream = new QRbitstream();

            

            foreach($this->items as $item) {

                $ret = $bstream->append($item->bstream);

                if($ret < 0) {

                    return null;

                }

            }



            return $bstream;

        }



        //----------------------------------------------------------------------

        public function getBitStream()

        {



            $bstream = $this->mergeBitStream();

            

            if($bstream == null) {

                return null;

            }

            

            $ret = $this->appendPaddingBit($bstream);

            if($ret < 0) {

                return null;

            }



            return $bstream;

        }

        

        //----------------------------------------------------------------------

        public function getByteStream()

        {

            $bstream = $this->getBitStream();

            if($bstream == null) {

                return null;

            }

            

            return $bstream->toByte();

        }

    }

        

        

    