commit b8606b24da7d6e979dd78bdf626de939e555543b Author: smiley Date: Tue Dec 1 17:55:20 2015 +0100 :octocat: diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..c93a2f4c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +################# +## PHPStorm +################# + +.idea/ + +################# +## Eclipse +################# + +.settings/ +.buildpath +.project + +################# +## Misc +################# + +pdoc.bat +composer.lock +docs +vendor \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..2541edba9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Smiley + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..b8b40c209 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# codemasher/php-qrcode + +[![Packagist](https://img.shields.io/packagist/v/codemasher/php-qrcode.svg?style=flat-square)](https://packagist.org/packages/codemasher/php-qrcode) +[![License](https://img.shields.io/packagist/l/codemasher/php-qrcode.svg?style=flat-square)](LICENSE) + +## Requirements +- PHP 5.6+, PHP 7 diff --git a/composer.json b/composer.json new file mode 100644 index 000000000..dfe6b51e3 --- /dev/null +++ b/composer.json @@ -0,0 +1,24 @@ +{ + "name": "codemasher/php-qrcode", + "description": "A QR code generator. PHP 5.6+, PHP 7", + "homepage": "https://github.com/codemasher/php-qrcode", + "license": "MIT", + "keywords": [ + "QR code", + "library" + ], + "authors": [ + { + "name": "Smiley", + "email": "smiley@chillerlan.net" + } + ], + "require": { + "php": ">=5.6.0" + }, + "autoload": { + "psr-4": { + "codemasher\\QRCode\\": "src/" + } + } +} diff --git a/examples/sample_custom.php b/examples/sample_custom.php new file mode 100644 index 000000000..21e68420a --- /dev/null +++ b/examples/sample_custom.php @@ -0,0 +1,22 @@ +getMinimumQRCode('イメージ作成(引数:サイズ,マージン', QRConst::ERROR_CORRECT_LEVEL_L); + + +header('Content-type: text/plain'); + +$m = $qr->getModuleCount(); + +for($row = 0; $row < $m; $row++){ + for($col = 0; $col < $m; $col++){ + echo $qr->isDark($row, $col) ? '#' : ' '; + } + echo PHP_EOL; +} + diff --git a/examples/sample_html.php b/examples/sample_html.php new file mode 100644 index 000000000..d1bb362a9 --- /dev/null +++ b/examples/sample_html.php @@ -0,0 +1,42 @@ +明示的に型番を指定'); + +// エラー訂正レベルを設定 +// QR_ERROR_CORRECT_LEVEL_L : 7% +// QR_ERROR_CORRECT_LEVEL_M : 15% +// QR_ERROR_CORRECT_LEVEL_Q : 25% +// QR_ERROR_CORRECT_LEVEL_H : 30% +$qrcode->setErrorCorrectLevel(QRConst::ERROR_CORRECT_LEVEL_L); + +// 型番(大きさ)を設定 +// 1~10 +$qrcode->setTypeNumber(3); + +// データ(文字列※)を設定 +// ※日本語はSJIS +$qrcode->addData('QRコード'); + +// QRコードを作成 +$qrcode->make(); + +// HTML出力 +$qrcode->printHTML(); + +//--------------------------------------------------------- +print('

型番自動

'); + +// 型番が最小となるQRコードを作成 +$qr = $qrcode->getMinimumQRCode('QRコード', QRConst::ERROR_CORRECT_LEVEL_L); + +// HTML出力 +$qr->printHTML(); \ No newline at end of file diff --git a/examples/sample_image.php b/examples/sample_image.php new file mode 100644 index 000000000..6f57ff69e --- /dev/null +++ b/examples/sample_image.php @@ -0,0 +1,18 @@ +getMinimumQRCode('QRコード', QRConst::ERROR_CORRECT_LEVEL_L); + +// イメージ作成(引数:サイズ,マージン) +$im = $qr->createImage(2, 4); + +header('Content-type: image/png'); +imagepng($im); + +imagedestroy($im); + diff --git a/src/BitBuffer.php b/src/BitBuffer.php new file mode 100644 index 000000000..876f5d961 --- /dev/null +++ b/src/BitBuffer.php @@ -0,0 +1,92 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace codemasher\QRCode; + +/** + * Class BitBuffer + */ +class BitBuffer{ + + /** + * @var array + */ + protected $buffer = []; + + /** + * @var int + */ + protected $length = 0; + + /** + * @return array + */ + public function getBuffer(){ + return $this->buffer; + } + + /** + * @return int + */ + public function getLengthInBits(){ + return $this->length; + } + + /** + * @return string + */ + public function __toString(){ + $buffer = ''; + + for($i = 0; $i < $this->getLengthInBits(); $i++){ + $buffer .= $this->get($i) ? '1' : '0'; + } + + return $buffer; + } + + /** + * @param $index + * + * @return bool + */ + public function get($index){ + return (($this->buffer[(int)floor($index / 8)] >> (7 - $index % 8))&1) === 1; + } + + /** + * @param $num + * @param $length + */ + public function put($num, $length){ + for($i = 0; $i < $length; $i++){ + $this->putBit((($num >> ($length - $i - 1))&1) === 1); + } + } + + /** + * @param $bit + */ + public function putBit($bit){ + $bufIndex = floor($this->length / 8); + + if(count($this->buffer) <= $bufIndex){ + $this->buffer[] = 0; + } + + if($bit){ + $this->buffer[(int)$bufIndex] |= (0x80 >> ($this->length % 8)); + } + + $this->length++; + } + +} diff --git a/src/Data/AlphaNum.php b/src/Data/AlphaNum.php new file mode 100644 index 000000000..df3fde931 --- /dev/null +++ b/src/Data/AlphaNum.php @@ -0,0 +1,106 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace codemasher\QRCode\Data; + +use codemasher\QRCode\BitBuffer; +use codemasher\QRCode\Data; +use codemasher\QRCode\Data\QRDataBase; +use codemasher\QRCode\Data\QRDataInterface; +use codemasher\QRCode\QRCodeException; +use codemasher\QRCode\QRConst; + +/** + * Class AlphaNum + */ +class AlphaNum extends QRDataBase implements QRDataInterface{ + + + /** + * AlphaNum constructor. + * + * @param $data + */ + public function __construct($data){ + parent::__construct($data, QRConst::MODE_ALPHA_NUM); + } + + /** + * @param $buffer + */ + public function write(BitBuffer &$buffer){ + + $i = 0; + $c = $this->getData(); + + while($i + 1 < strlen($c)){ + $buffer->put($this->getCode(ord($c[$i])) * 45 + + $this->getCode(ord($c[$i + 1])), 11); + $i += 2; + } + + if($i < strlen($c)){ + $buffer->put($this->getCode(ord($c[$i])), 6); + } + } + + /** + * @return int + */ + public function getLength(){ + return strlen($this->getData()); + } + + /** + * @param $c + * + * @return int + * @throws \codemasher\QRCode\QRCodeException + */ + protected function getCode($c){ + + if($this->util->toCharCode('0') <= $c + && $c <= $this->util->toCharCode('9') + ){ + return $c - $this->util->toCharCode('0'); + } + else if($this->util->toCharCode('A') <= $c + && $c <= $this->util->toCharCode('Z') + ){ + return $c - $this->util->toCharCode('A') + 10; + } + else{ + switch($c){ + case $this->util->toCharCode(' ') : + return 36; + case $this->util->toCharCode('$') : + return 37; + case $this->util->toCharCode('%') : + return 38; + case $this->util->toCharCode('*') : + return 39; + case $this->util->toCharCode('+') : + return 40; + case $this->util->toCharCode('-') : + return 41; + case $this->util->toCharCode('.') : + return 42; + case $this->util->toCharCode('/') : + return 43; + case $this->util->toCharCode(':') : + return 44; + default : + throw new QRCodeException('illegal char: '.$c); + } + } + + } +} diff --git a/src/Data/EightBitByte.php b/src/Data/EightBitByte.php new file mode 100644 index 000000000..3f137b368 --- /dev/null +++ b/src/Data/EightBitByte.php @@ -0,0 +1,51 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace codemasher\QRCode\Data; + +use codemasher\QRCode\BitBuffer; +use codemasher\QRCode\QRConst; +use codemasher\QRCode\Data\QRDataBase; +use codemasher\QRCode\Data\QRDataInterface; + + +/** + * Class EightBitByte + */ +class EightBitByte extends QRDataBase implements QRDataInterface{ + + /** + * EightBitByte constructor. + * + * @param $data + */ + public function __construct($data){ + parent::__construct($data, QRConst::MODE_8BIT_BYTE); + } + + /** + * @param $buffer + */ + public function write(BitBuffer &$buffer){ + $data = $this->getData(); + + for($i = 0; $i < strlen($data); $i++){ + $buffer->put(ord($data[$i]), 8); + } + } + + /** + * @return int + */ + public function getLength(){ + return strlen($this->getData()); + } +} \ No newline at end of file diff --git a/src/Data/Kanji.php b/src/Data/Kanji.php new file mode 100644 index 000000000..90c644955 --- /dev/null +++ b/src/Data/Kanji.php @@ -0,0 +1,72 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace codemasher\QRCode\Data; + +use codemasher\QRCode\BitBuffer; +use codemasher\QRCode\Data\QRDataBase; +use codemasher\QRCode\Data\QRDataInterface; +use codemasher\QRCode\QRCodeException; +use codemasher\QRCode\QRConst; + +/** + * Class Kanji + */ +class Kanji extends QRDataBase implements QRDataInterface{ + + /** + * Kanji constructor. + * + * @param $data + */ + public function __construct($data){ + parent::__construct($data, QRConst::MODE_KANJI); + } + + /** + * @param $buffer + * + * @throws \codemasher\QRCode\QRCodeException + */ + public function write(BitBuffer &$buffer){ + $data = $this->getData(); + + $i = 0; + while($i + 1 < strlen($data)){ + $c = ((0xff&ord($data[$i])) << 8)|(0xff&ord($data[$i + 1])); + + if(0x8140 <= $c && $c <= 0x9FFC){ + $c -= 0x8140; + } + else if(0xE040 <= $c && $c <= 0xEBBF){ + $c -= 0xC140; + } + else{ + throw new QRCodeException('illegal char at '.($i + 1).' ('.$c.')'); + } + + $c = (($c >> 8)&0xff) * 0xC0 + ($c&0xff); + $buffer->put($c, 13); + $i += 2; + } + + if($i < strlen($data)){ + throw new QRCodeException('illegal char at '.($i + 1)); + } + } + + /** + * @return float + */ + public function getLength(){ + return floor(strlen($this->getData()) / 2); + } +} diff --git a/src/Data/Number.php b/src/Data/Number.php new file mode 100644 index 000000000..162f3830c --- /dev/null +++ b/src/Data/Number.php @@ -0,0 +1,101 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace codemasher\QRCode\Data; + +use codemasher\QRCode\BitBuffer; +use codemasher\QRCode\Data\QRDataBase; +use codemasher\QRCode\Data\QRDataInterface; +use codemasher\QRCode\QRCodeException; +use codemasher\QRCode\QRConst; + +/** + * Class Number + */ +class Number extends QRDataBase implements QRDataInterface{ + + /** + * @var \codemasher\QRCode\Util + */ + protected $util; + + /** + * Number constructor. + * + * @param $data + */ + public function __construct($data){ + parent::__construct($data, QRConst::MODE_NUMBER); + } + + /** + * @param $buffer + */ + public function write(BitBuffer &$buffer){ + $data = $this->getData(); + + $i = 0; + while($i + 2 < strlen($data)){ + $num = $this->parseInt(substr($data, $i, 3)); + $buffer->put($num, 10); + $i += 3; + } + + if($i < strlen($data)){ + + if(strlen($data) - $i == 1){ + $num = $this->parseInt(substr($data, $i, $i + 1)); + $buffer->put($num, 4); + } + else if(strlen($data) - $i == 2){ + $num = $this->parseInt(substr($data, $i, $i + 2)); + $buffer->put($num, 7); + } + } + } + + /** + * @return int + */ + public function getLength(){ + return strlen($this->getData()); + } + + /** + * @param $s + * + * @return int + */ + protected function parseInt($s){ + $num = 0; + + for($i = 0; $i < strlen($s); $i++){ + $num = $num * 10 + $this->parseIntAt(ord($s[$i])); + } + + return $num; + } + + /** + * @param $c + * + * @return mixed + * @throws \codemasher\QRCode\QRCodeException + */ + protected function parseIntAt($c){ + if($this->util->toCharCode('0') <= $c && $c <= $this->util->toCharCode('9')){ + return $c - $this->util->toCharCode('0'); + } + + throw new QRCodeException('illegal char: '.$c); + } + +} diff --git a/src/Data/QRDataBase.php b/src/Data/QRDataBase.php new file mode 100644 index 000000000..4e67c9e3b --- /dev/null +++ b/src/Data/QRDataBase.php @@ -0,0 +1,132 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace codemasher\QRCode\Data; + +use codemasher\QRCode\BitBuffer; +use codemasher\QRCode\QRConst; +use codemasher\QRCode\QRCodeException; +use codemasher\QRCode\Util; + +/** + * Class QRDataBase + */ +class QRDataBase{ + + /** + * @var + */ + protected $mode; + + /** + * @var + */ + protected $data; + + /** + * @var \codemasher\QRCode\Util + */ + protected $util; + + /** + * QRDataBase constructor. + * + * @param $data + * @param $mode + */ + public function __construct($data, $mode){ + $this->data = $data; + $this->mode = $mode; + $this->util = new Util; + } + + /** + * @return mixed + */ + public function getMode(){ + return $this->mode; + } + + /** + * @return mixed + */ + public function getData(){ + return $this->data; + } + + /** + * @throws \codemasher\QRCode\QRCodeException + */ + public function getLength(){ + throw new QRCodeException('not implemented.'); + } + + /** + * @param $buffer + * + * @throws \codemasher\QRCode\QRCodeException + */ + public function write(BitBuffer &$buffer){ + throw new QRCodeException('not implemented.'); + } + + /** + * @param $type + * + * @return int + * @throws \codemasher\QRCode\QRCodeException + */ + public function getLengthInBits($type){ + if(1 <= $type && $type < 10){ + + // 1 - 9 + switch($this->mode){ + case QRConst::MODE_NUMBER : return 10; + case QRConst::MODE_ALPHA_NUM : return 9; + case QRConst::MODE_8BIT_BYTE : return 8; + case QRConst::MODE_KANJI : return 8; + default : + throw new QRCodeException('mode: '.$this->mode); + } + + } + else if($type < 27){ + + // 10 - 26 + switch($this->mode){ + case QRConst::MODE_NUMBER : return 12; + case QRConst::MODE_ALPHA_NUM : return 11; + case QRConst::MODE_8BIT_BYTE : return 16; + case QRConst::MODE_KANJI : return 10; + default : + throw new QRCodeException('mode: '.$this->mode); + } + + } + else if($type < 41){ + + // 27 - 40 + switch($this->mode){ + case QRConst::MODE_NUMBER : return 14; + case QRConst::MODE_ALPHA_NUM : return 13; + case QRConst::MODE_8BIT_BYTE : return 16; + case QRConst::MODE_KANJI : return 12; + default : + throw new QRCodeException('mode: '.$this->mode); + } + + } + else{ + throw new QRCodeException('mode: '.$this->mode); + } + } + +} diff --git a/src/Data/QRDataInterface.php b/src/Data/QRDataInterface.php new file mode 100644 index 000000000..fe740bb03 --- /dev/null +++ b/src/Data/QRDataInterface.php @@ -0,0 +1,47 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace codemasher\QRCode\Data; + +use codemasher\QRCode\BitBuffer; + +/** + * Interface QRDataInterface + */ +interface QRDataInterface{ + + /** + * @param \codemasher\QRCode\BitBuffer $buffer + */ + public function write(BitBuffer &$buffer); + + /** + * @return int + */ + public function getLength(); + + /** + * @return mixed + */ + public function getData(); + + /** + * @return mixed + */ + public function getMode(); + + /** + * @param $type + * + * @return int + * @throws \codemasher\QRCode\QRCodeException + */ + public function getLengthInBits($type); +} \ No newline at end of file diff --git a/src/Math.php b/src/Math.php new file mode 100644 index 000000000..0dac5160a --- /dev/null +++ b/src/Math.php @@ -0,0 +1,104 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace codemasher\QRCode; + +use codemasher\QRCode\QRCodeException; + +/** + * Class Math + */ +class Math{ + + /** + * @var array + */ + protected $QR_MATH_EXP_TABLE; + + /** + * @var array + */ + protected $QR_MATH_LOG_TABLE; + + /** + * Math constructor. + */ + public function __construct(){ + $this->QR_MATH_EXP_TABLE = $this->createNumArray(256); + + for($i = 0; $i < 8; $i++){ + $this->QR_MATH_EXP_TABLE[$i] = 1 << $i; + } + + for($i = 8; $i < 256; $i++){ + $this->QR_MATH_EXP_TABLE[$i] = + $this->QR_MATH_EXP_TABLE[$i - 4] + ^$this->QR_MATH_EXP_TABLE[$i - 5] + ^$this->QR_MATH_EXP_TABLE[$i - 6] + ^$this->QR_MATH_EXP_TABLE[$i - 8]; + } + + $this->QR_MATH_LOG_TABLE = $this->createNumArray(256); + + for($i = 0; $i < 255; $i++){ + $this->QR_MATH_LOG_TABLE[$this->QR_MATH_EXP_TABLE[$i]] = $i; + } + } + + /** + * @param int $length + * + * @return array + */ + public function createNumArray($length){ + $num_array = []; + + for($i = 0; $i < $length; $i++){ + $num_array[] = 0; + } + + return $num_array; + } + + /** + * @param int $n + * + * @return mixed + * @throws \codemasher\QRCode\QRCodeException + */ + public function glog($n){ + + if($n < 1){ + throw new QRCodeException('log('.$n.')'); + } + + return $this->QR_MATH_LOG_TABLE[$n]; + } + + /** + * @param int $n + * + * @return mixed + */ + public function gexp($n){ + + while($n < 0){ + $n += 255; + } + + while($n >= 256){ + $n -= 255; + } + + return $this->QR_MATH_EXP_TABLE[$n]; + } + +} diff --git a/src/Polynomial.php b/src/Polynomial.php new file mode 100644 index 000000000..7387643e8 --- /dev/null +++ b/src/Polynomial.php @@ -0,0 +1,150 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace codemasher\QRCode; + +use codemasher\QRCode\Math; + +/** + * Class Polynomial + */ +class Polynomial{ + + /** + * @var + */ + protected $num; + + /** + * @var \codemasher\QRCode\Math + */ + protected $math; + + /** + * Polynomial constructor. + * + * @param array $num + * @param int $shift + */ + public function __construct(array $num, $shift = 0){ + $this->math = new Math; + + $offset = 0; + while($offset < count($num) && $num[$offset] === 0){ + $offset++; + } + + $this->num = $this->math->createNumArray(count($num) - $offset + $shift); + for($i = 0; $i < count($num) - $offset; $i++){ + $this->num[$i] = $num[$i + $offset]; + } + } + + /** + * @param $index + * @todo rename! + * + * @return mixed + */ + public function get($index){ + return $this->num[$index]; + } + + /** + * @return int + */ + public function getLength(){ + return count($this->num); + } + + /** + * PHP5 + * @return string + */ + public function __toString(){ + return $this->toString(); + } + + /** + * @return string + */ + public function toString(){ + $buffer = ''; + + for($i = 0; $i < $this->getLength(); $i++){ + if($i > 0){ + $buffer .= ','; + } + $buffer .= $this->get($i); + } + + return $buffer; + } + + /** + * @return string + */ + public function toLogString(){ + $buffer = ''; + + for($i = 0; $i < $this->getLength(); $i++){ + if($i > 0){ + $buffer .= ','; + } + $buffer .= $this->math->glog($this->get($i)); + } + + return $buffer; + } + + /** + * @param \codemasher\QRCode\Polynomial $e + * + * @return \codemasher\QRCode\Polynomial + */ + public function multiply($e){ + $num = $this->math->createNumArray($this->getLength() + $e->getLength() - 1); + + for($i = 0; $i < $this->getLength(); $i++){ + for($j = 0; $j < $e->getLength(); $j++){ + $num[$i + $j] ^= $this->math->gexp($this->math->glog($this->get($i)) + $this->math->glog($e->get($j))); + } + } + + return new Polynomial($num); + } + + /** + * @param \codemasher\QRCode\Polynomial $e + * + * @return $this|\codemasher\QRCode\Polynomial + */ + public function mod($e){ + + if($this->getLength() - $e->getLength() < 0){ + return $this; + } + + $ratio = $this->math->glog($this->get(0)) - $this->math->glog($e->get(0)); + + $num = $this->math->createNumArray($this->getLength()); + for($i = 0; $i < $this->getLength(); $i++){ + $num[$i] = $this->get($i); + } + + for($i = 0; $i < $e->getLength(); $i++){ + $num[$i] ^= $this->math->gexp($this->math->glog($e->get($i)) + $ratio); + } + + return (new Polynomial($num))->mod($e); + } + +} diff --git a/src/QRCode.php b/src/QRCode.php new file mode 100644 index 000000000..3fb331287 --- /dev/null +++ b/src/QRCode.php @@ -0,0 +1,597 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace codemasher\QRCode; +use codemasher\QRCode\Data\AlphaNum; +use codemasher\QRCode\Data\EightBitByte; +use codemasher\QRCode\Data\Kanji; +use codemasher\QRCode\Data\Number; +use codemasher\QRCode\QRConst; + +/** + * Class QRCode + */ +class QRCode{ + + const QR_PAD0 = 0xEC; + const QR_PAD1 = 0x11; + + protected $typeNumber; + + protected $modules; + + protected $moduleCount; + + protected $errorCorrectLevel; + + protected $qrDataList; + + protected $util; + + protected $rs_block; + + public function __construct(){ + $this->typeNumber = 1; + $this->errorCorrectLevel = QRConst::ERROR_CORRECT_LEVEL_H; + $this->qrDataList = []; + $this->util = new Util; + $this->rs_block = new RSBlock; + } + + public function getTypeNumber(){ + return $this->typeNumber; + } + + public function setTypeNumber($typeNumber){ + $this->typeNumber = $typeNumber; + } + + public function getErrorCorrectLevel(){ + return $this->errorCorrectLevel; + } + + public function setErrorCorrectLevel($errorCorrectLevel){ + $this->errorCorrectLevel = $errorCorrectLevel; + } + + public function addData($data, $mode = 0){ + + if($mode == 0){ + $mode = $this->util->getMode($data); + } + + switch($mode){ + + case QRConst::MODE_NUMBER : + $this->addDataImpl(new Number($data)); + break; + + case QRConst::MODE_ALPHA_NUM : + $this->addDataImpl(new AlphaNum($data)); + break; + + case QRConst::MODE_8BIT_BYTE : + $this->addDataImpl(new EightBitByte($data)); + break; + + case QRConst::MODE_KANJI : + $this->addDataImpl(new Kanji($data)); + break; + + default : + trigger_error("mode:$mode", E_USER_ERROR); + } + } + + public function clearData(){ + $this->qrDataList = []; + } + + public function addDataImpl(&$qrData){ + $this->qrDataList[] = $qrData; + } + + public function getDataCount(){ + return count($this->qrDataList); + } + + public function getData($index){ + return $this->qrDataList[$index]; + } + + public function isDark($row, $col){ + if($this->modules[$row][$col] !== null){ + return $this->modules[$row][$col]; + } + else{ + return false; + } + } + + public function getModuleCount(){ + return $this->moduleCount; + } + + // used for converting fg/bg colors (e.g. #0000ff = 0x0000FF) + // added 2015.07.27 ~ DoktorJ + public function hex2rgb($hex = 0x0){ + return [ + 'r' => floor($hex / 65536), + 'g' => floor($hex / 256) % 256, + 'b' => $hex % 256, + ]; + } + + public function make(){ + $this->makeImpl(false, $this->getBestMaskPattern()); + } + + public function getBestMaskPattern(){ + + $minLostPoint = 0; + $pattern = 0; + + for($i = 0; $i < 8; $i++){ + + $this->makeImpl(true, $i); + + $lostPoint = $this->util->getLostPoint($this); + + if($i == 0 || $minLostPoint > $lostPoint){ + $minLostPoint = $lostPoint; + $pattern = $i; + } + } + + return $pattern; + } + + public function createNullArray($length){ + $nullArray = []; + for($i = 0; $i < $length; $i++){ + $nullArray[] = null; + } + return $nullArray; + } + + public function makeImpl($test, $maskPattern){ + + $this->moduleCount = $this->typeNumber * 4 + 17; + + $this->modules = []; + for($i = 0; $i < $this->moduleCount; $i++){ + $this->modules[] = $this->createNullArray($this->moduleCount); + } + + $this->setupPositionProbePattern(0, 0); + $this->setupPositionProbePattern($this->moduleCount - 7, 0); + $this->setupPositionProbePattern(0, $this->moduleCount - 7); + + $this->setupPositionAdjustPattern(); + $this->setupTimingPattern(); + + $this->setupTypeInfo($test, $maskPattern); + + if($this->typeNumber >= 7){ + $this->setupTypeNumber($test); + } + + $dataArray = $this->qrDataList; + + $data = $this->createData($this->typeNumber, $this->errorCorrectLevel, $dataArray); + + $this->mapData($data, $maskPattern); + } + + public function mapData(&$data, $maskPattern){ + + $inc = -1; + $row = $this->moduleCount - 1; + $bitIndex = 7; + $byteIndex = 0; + + for($col = $this->moduleCount - 1; $col > 0; $col -= 2){ + + if($col == 6){ + $col--; + } + + while(true){ + + for($c = 0; $c < 2; $c++){ + + if($this->modules[$row][$col - $c] === null){ + + $dark = false; + + if($byteIndex < count($data)){ + $dark = ((($data[$byteIndex] >> $bitIndex)&1) == 1); + } + + $mask = $this->util->getMask($maskPattern, $row, $col - $c); + + if($mask){ + $dark = !$dark; + } + + $this->modules[$row][$col - $c] = $dark; + $bitIndex--; + + if($bitIndex == -1){ + $byteIndex++; + $bitIndex = 7; + } + } + } + + $row += $inc; + + if($row < 0 || $this->moduleCount <= $row){ + $row -= $inc; + $inc = -$inc; + break; + } + } + } + } + + public function setupPositionAdjustPattern(){ + + $pos = $this->util->getPatternPosition($this->typeNumber); + + for($i = 0; $i < count($pos); $i++){ + + for($j = 0; $j < count($pos); $j++){ + + $row = $pos[$i]; + $col = $pos[$j]; + + if($this->modules[$row][$col] !== null){ + continue; + } + + for($r = -2; $r <= 2; $r++){ + + for($c = -2; $c <= 2; $c++){ + + if($r == -2 || $r == 2 || $c == -2 || $c == 2 + || ($r == 0 && $c == 0) + ){ + $this->modules[$row + $r][$col + $c] = true; + } + else{ + $this->modules[$row + $r][$col + $c] = false; + } + } + } + } + } + } + + public function setupPositionProbePattern($row, $col){ + + for($r = -1; $r <= 7; $r++){ + + for($c = -1; $c <= 7; $c++){ + + if($row + $r <= -1 || $this->moduleCount <= $row + $r + || $col + $c <= -1 || $this->moduleCount <= $col + $c + ){ + continue; + } + + if((0 <= $r && $r <= 6 && ($c == 0 || $c == 6)) + || (0 <= $c && $c <= 6 && ($r == 0 || $r == 6)) + || (2 <= $r && $r <= 4 && 2 <= $c && $c <= 4) + ){ + $this->modules[$row + $r][$col + $c] = true; + } + else{ + $this->modules[$row + $r][$col + $c] = false; + } + } + } + } + + public function setupTimingPattern(){ + + for($r = 8; $r < $this->moduleCount - 8; $r++){ + if($this->modules[$r][6] !== null){ + continue; + } + $this->modules[$r][6] = ($r % 2 == 0); + } + + for($c = 8; $c < $this->moduleCount - 8; $c++){ + if($this->modules[6][$c] !== null){ + continue; + } + $this->modules[6][$c] = ($c % 2 == 0); + } + } + + public function setupTypeNumber($test){ + + $bits = $this->util->getBCHTypeNumber($this->typeNumber); + + for($i = 0; $i < 18; $i++){ + $mod = (!$test && (($bits >> $i)&1) == 1); + $this->modules[(int)floor($i / 3)][$i % 3 + $this->moduleCount - 8 - 3] = $mod; + } + + for($i = 0; $i < 18; $i++){ + $mod = (!$test && (($bits >> $i)&1) == 1); + $this->modules[$i % 3 + $this->moduleCount - 8 - 3][(int)floor($i / 3)] = $mod; + } + } + + public function setupTypeInfo($test, $maskPattern){ + + $data = ($this->errorCorrectLevel << 3)|$maskPattern; + $bits = $this->util->getBCHTypeInfo($data); + + for($i = 0; $i < 15; $i++){ + + $mod = (!$test && (($bits >> $i)&1) == 1); + + if($i < 6){ + $this->modules[$i][8] = $mod; + } + else if($i < 8){ + $this->modules[$i + 1][8] = $mod; + } + else{ + $this->modules[$this->moduleCount - 15 + $i][8] = $mod; + } + } + + for($i = 0; $i < 15; $i++){ + + $mod = (!$test && (($bits >> $i)&1) == 1); + + if($i < 8){ + $this->modules[8][$this->moduleCount - $i - 1] = $mod; + } + else if($i < 9){ + $this->modules[8][15 - $i - 1 + 1] = $mod; + } + else{ + $this->modules[8][15 - $i - 1] = $mod; + } + } + + $this->modules[$this->moduleCount - 8][8] = !$test; + } + + public function createData($typeNumber, $errorCorrectLevel, $dataArray){ + + $rsBlocks = $this->rs_block->getRSBlocks($typeNumber, $errorCorrectLevel); + + $buffer = new BitBuffer; + + for($i = 0; $i < count($dataArray); $i++){ + $data = $dataArray[$i]; + $buffer->put($data->getMode(), 4); + $buffer->put($data->getLength(), $data->getLengthInBits($typeNumber)); + $data->write($buffer); + } + + $totalDataCount = 0; + for($i = 0; $i < count($rsBlocks); $i++){ + $totalDataCount += $rsBlocks[$i]->getDataCount(); + } + + if($buffer->getLengthInBits() > $totalDataCount * 8){ + trigger_error("code length overflow. (" + .$buffer->getLengthInBits() + .">" + .$totalDataCount * 8 + .")", E_USER_ERROR); + } + + // end code. + if($buffer->getLengthInBits() + 4 <= $totalDataCount * 8){ + $buffer->put(0, 4); + } + + // padding + while($buffer->getLengthInBits() % 8 != 0){ + $buffer->putBit(false); + } + + // padding + while(true){ + + if($buffer->getLengthInBits() >= $totalDataCount * 8){ + break; + } + $buffer->put(self::QR_PAD0, 8); + + if($buffer->getLengthInBits() >= $totalDataCount * 8){ + break; + } + $buffer->put(self::QR_PAD1, 8); + } + + return $this->createBytes($buffer, $rsBlocks); + } + + public function createBytes(&$buffer, &$rsBlocks){ + + $offset = 0; + + $maxDcCount = 0; + $maxEcCount = 0; + + $dcdata = $this->createNullArray(count($rsBlocks)); + $ecdata = $this->createNullArray(count($rsBlocks)); + + for($r = 0; $r < count($rsBlocks); $r++){ + + $dcCount = $rsBlocks[$r]->getDataCount(); + $ecCount = $rsBlocks[$r]->getTotalCount() - $dcCount; + + $maxDcCount = max($maxDcCount, $dcCount); + $maxEcCount = max($maxEcCount, $ecCount); + + $dcdata[$r] = $this->createNullArray($dcCount); + for($i = 0; $i < count($dcdata[$r]); $i++){ + $bdata = $buffer->getBuffer(); + $dcdata[$r][$i] = 0xff&$bdata[$i + $offset]; + } + $offset += $dcCount; + + $rsPoly = $this->util->getErrorCorrectPolynomial($ecCount); + $rawPoly = new Polynomial($dcdata[$r], $rsPoly->getLength() - 1); + + $modPoly = $rawPoly->mod($rsPoly); + $ecdata[$r] = $this->createNullArray($rsPoly->getLength() - 1); + for($i = 0; $i < count($ecdata[$r]); $i++){ + $modIndex = $i + $modPoly->getLength() - count($ecdata[$r]); + $ecdata[$r][$i] = ($modIndex >= 0) ? $modPoly->get($modIndex) : 0; + } + } + + $totalCodeCount = 0; + for($i = 0; $i < count($rsBlocks); $i++){ + $totalCodeCount += $rsBlocks[$i]->getTotalCount(); + } + + $data = $this->createNullArray($totalCodeCount); + + $index = 0; + + for($i = 0; $i < $maxDcCount; $i++){ + for($r = 0; $r < count($rsBlocks); $r++){ + if($i < count($dcdata[$r])){ + $data[$index++] = $dcdata[$r][$i]; + } + } + } + + for($i = 0; $i < $maxEcCount; $i++){ + for($r = 0; $r < count($rsBlocks); $r++){ + if($i < count($ecdata[$r])){ + $data[$index++] = $ecdata[$r][$i]; + } + } + } + + return $data; + } + + public function getMinimumQRCode($data, $errorCorrectLevel){ + + $mode = $this->util->getMode($data); + + $qr = new QRCode(); + $qr->setErrorCorrectLevel($errorCorrectLevel); + $qr->addData($data, $mode); + + $qrData = $qr->getData(0); + $length = $qrData->getLength(); + + for($typeNumber = 1; $typeNumber <= 10; $typeNumber++){ + if($length <= $this->util->getMaxLength($typeNumber, $mode, $errorCorrectLevel)){ + $qr->setTypeNumber($typeNumber); + break; + } + } + + $qr->make(); + + return $qr; + } + + // added $fg (foreground), $bg (background), and $bgtrans (use transparent bg) parameters + // also added some simple error checking on parameters + // updated 2015.07.27 ~ DoktorJ + public function createImage($size = 2, $margin = 2, $fg = 0x000000, $bg = 0xFFFFFF, $bgtrans = false){ + + // size/margin EC + if(!is_numeric($size)){ + $size = 2; + } + if(!is_numeric($margin)){ + $margin = 2; + } + if($size < 1){ + $size = 1; + } + if($margin < 0){ + $margin = 0; + } + + $image_size = $this->getModuleCount() * $size + $margin * 2; + + $image = imagecreatetruecolor($image_size, $image_size); + + // fg/bg EC + if($fg < 0 || $fg > 0xFFFFFF){ + $fg = 0x0; + } + if($bg < 0 || $bg > 0xFFFFFF){ + $bg = 0xFFFFFF; + } + + // convert hexadecimal RGB to arrays for imagecolorallocate + $fgrgb = $this->hex2rgb($fg); + $bgrgb = $this->hex2rgb($bg); + + // replace $black and $white with $fgc and $bgc + $fgc = imagecolorallocate($image, $fgrgb['r'], $fgrgb['g'], $fgrgb['b']); + $bgc = imagecolorallocate($image, $bgrgb['r'], $bgrgb['g'], $bgrgb['b']); + if($bgtrans){ + imagecolortransparent($image, $bgc); + } + + // update $white to $bgc + imagefilledrectangle($image, 0, 0, $image_size, $image_size, $bgc); + + for($r = 0; $r < $this->getModuleCount(); $r++){ + for($c = 0; $c < $this->getModuleCount(); $c++){ + if($this->isDark($r, $c)){ + + // update $black to $fgc + imagefilledrectangle($image, + $margin + $c * $size, + $margin + $r * $size, + $margin + ($c + 1) * $size - 1, + $margin + ($r + 1) * $size - 1, + $fgc); + } + } + } + + return $image; + } + + public function printHTML($size = "2px"){ + + $style = "border-style:none;border-collapse:collapse;margin:0px;padding:0px;"; + + print(""); + + for($r = 0; $r < $this->getModuleCount(); $r++){ + + print(""); + + for($c = 0; $c < $this->getModuleCount(); $c++){ + $color = $this->isDark($r, $c) ? "#000000" : "#ffffff"; + print(""); + } + + print(""); + } + + print("
"); + } +} diff --git a/src/QRCodeException.php b/src/QRCodeException.php new file mode 100644 index 000000000..c67aa8267 --- /dev/null +++ b/src/QRCodeException.php @@ -0,0 +1,22 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace codemasher\QRCode; + +use Exception; + +/** + * Placeholder + */ +class QRCodeException extends Exception{ + +} diff --git a/src/QRConst.php b/src/QRConst.php new file mode 100644 index 000000000..22f34b546 --- /dev/null +++ b/src/QRConst.php @@ -0,0 +1,38 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace codemasher\QRCode; + +/** + * Class QRConst + */ +class QRConst{ + + const MODE_NUMBER = 1 << 0; + const MODE_ALPHA_NUM = 1 << 1; + const MODE_8BIT_BYTE = 1 << 2; + const MODE_KANJI = 1 << 3; + + const MASK_PATTERN000 = 0; + const MASK_PATTERN001 = 1; + const MASK_PATTERN010 = 2; + const MASK_PATTERN011 = 3; + const MASK_PATTERN100 = 4; + const MASK_PATTERN101 = 5; + const MASK_PATTERN110 = 6; + const MASK_PATTERN111 = 7; + + const ERROR_CORRECT_LEVEL_L = 1; // 7%. + const ERROR_CORRECT_LEVEL_M = 0; // 15%. + const ERROR_CORRECT_LEVEL_Q = 3; // 25%. + const ERROR_CORRECT_LEVEL_H = 2; // 30%. + +} diff --git a/src/RSBlock.php b/src/RSBlock.php new file mode 100644 index 000000000..22902da61 --- /dev/null +++ b/src/RSBlock.php @@ -0,0 +1,180 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace codemasher\QRCode; + +use codemasher\QRCode\QRConst; +use codemasher\QRCode\QRCodeException; + +/** + * Class RSBlock + */ +class RSBlock{ + + /** + * @var int + */ + protected $totalCount; + + /** + * @var int + */ + protected $dataCount; + + /** + * @var array + */ + protected $QR_RS_BLOCK_TABLE = [ + + // L + // M + // Q + // H + + // 1 + [1, 26, 19], + [1, 26, 16], + [1, 26, 13], + [1, 26, 9], + + // 2 + [1, 44, 34], + [1, 44, 28], + [1, 44, 22], + [1, 44, 16], + + // 3 + [1, 70, 55], + [1, 70, 44], + [2, 35, 17], + [2, 35, 13], + + // 4 + [1, 100, 80], + [2, 50, 32], + [2, 50, 24], + [4, 25, 9], + + // 5 + [1, 134, 108], + [2, 67, 43], + [2, 33, 15, 2, 34, 16], + [2, 33, 11, 2, 34, 12], + + // 6 + [2, 86, 68], + [4, 43, 27], + [4, 43, 19], + [4, 43, 15], + + // 7 + [2, 98, 78], + [4, 49, 31], + [2, 32, 14, 4, 33, 15], + [4, 39, 13, 1, 40, 14], + + // 8 + [2, 121, 97], + [2, 60, 38, 2, 61, 39], + [4, 40, 18, 2, 41, 19], + [4, 40, 14, 2, 41, 15], + + // 9 + [2, 146, 116], + [3, 58, 36, 2, 59, 37], + [4, 36, 16, 4, 37, 17], + [4, 36, 12, 4, 37, 13], + + // 10 + [2, 86, 68, 2, 87, 69], + [4, 69, 43, 1, 70, 44], + [6, 43, 19, 2, 44, 20], + [6, 43, 15, 2, 44, 16], + + ]; + + + public function __construct($totalCount = null, $dataCount = null){ + if(isset($totalCount, $dataCount)){ + $this->setCount($totalCount, $dataCount); + } + } + + /** + * RSBlock constructor. + * + * @param int $totalCount + * @param int $dataCount + */ + public function setCount($totalCount, $dataCount){ + $this->totalCount = $totalCount; + $this->dataCount = $dataCount; + } + + /** + * @return int + */ + public function getDataCount(){ + return $this->dataCount; + } + + /** + * @return int + */ + public function getTotalCount(){ + return $this->totalCount; + } + + /** + * @param $typeNumber + * @param $errorCorrectLevel + * + * @return array + */ + public function getRSBlocks($typeNumber, $errorCorrectLevel){ + $rsBlock = $this->getRsBlockTable($typeNumber, $errorCorrectLevel); + $length = count($rsBlock) / 3; + $list = []; + + for($i = 0; $i < $length; $i++){ + $count = $rsBlock[$i * 3 + 0]; + $totalCount = $rsBlock[$i * 3 + 1]; + $dataCount = $rsBlock[$i * 3 + 2]; + + for($j = 0; $j < $count; $j++){ + $list[] = new RSBlock($totalCount, $dataCount); + } + } + + return $list; + } + + /** + * @param $typeNumber + * @param $errorCorrectLevel + * + * @return mixed + * @throws \codemasher\QRCode\QRCodeException + */ + public function getRsBlockTable($typeNumber, $errorCorrectLevel){ + + switch($errorCorrectLevel){ + case QRConst::ERROR_CORRECT_LEVEL_L: return $this->QR_RS_BLOCK_TABLE[($typeNumber - 1) * 4 + 0]; + case QRConst::ERROR_CORRECT_LEVEL_M: return $this->QR_RS_BLOCK_TABLE[($typeNumber - 1) * 4 + 1]; + case QRConst::ERROR_CORRECT_LEVEL_Q: return $this->QR_RS_BLOCK_TABLE[($typeNumber - 1) * 4 + 2]; + case QRConst::ERROR_CORRECT_LEVEL_H: return $this->QR_RS_BLOCK_TABLE[($typeNumber - 1) * 4 + 3]; + default: + throw new QRCodeException('tn:'.$typeNumber.'/ecl:'.$errorCorrectLevel); + } + + } + +} diff --git a/src/Util.php b/src/Util.php new file mode 100644 index 000000000..147f664c1 --- /dev/null +++ b/src/Util.php @@ -0,0 +1,466 @@ + + * @copyright 2015 Smiley + * @license MIT + */ + +namespace codemasher\QRCode; + +use codemasher\QRCode\Math; +use codemasher\QRCode\Polynomial; +use codemasher\QRCode\QRCode; +use codemasher\QRCode\QRConst; +use codemasher\QRCode\QRCodeException; + +/** + * Class Util + */ +class Util{ + + const QR_G15 = (1 << 10)|(1 << 8)|(1 << 5)|(1 << 4)|(1 << 2)|(1 << 1)|(1 << 0); + const QR_G18 = (1 << 12)|(1 << 11)|(1 << 10)|(1 << 9)|(1 << 8)|(1 << 5)|(1 << 2)|(1 << 0); + const QR_G15_MASK = (1 << 14)|(1 << 12)|(1 << 10)|(1 << 4)|(1 << 1); + + /** + * @var array + */ + protected $QR_MAX_LENGTH = [ + [[41, 25, 17, 10], [34, 20, 14, 8], [27, 16, 11, 7], [17, 10, 7, 4]], + [[77, 47, 32, 20], [63, 38, 26, 16], [48, 29, 20, 12], [34, 20, 14, 8]], + [[127, 77, 53, 32], [101, 61, 42, 26], [77, 47, 32, 20], [58, 35, 24, 15]], + [[187, 114, 78, 48], [149, 90, 62, 38], [111, 67, 46, 28], [82, 50, 34, 21]], + [[255, 154, 106, 65], [202, 122, 84, 52], [144, 87, 60, 37], [106, 64, 44, 27]], + [[322, 195, 134, 82], [255, 154, 106, 65], [178, 108, 74, 45], [139, 84, 58, 36]], + [[370, 224, 154, 95], [293, 178, 122, 75], [207, 125, 86, 53], [154, 93, 64, 39]], + [[461, 279, 192, 118], [365, 221, 152, 93], [259, 157, 108, 66], [202, 122, 84, 52]], + [[552, 335, 230, 141], [432, 262, 180, 111], [312, 189, 130, 80], [235, 143, 98, 60]], + [[652, 395, 271, 167], [513, 311, 213, 131], [364, 221, 151, 93], [288, 174, 119, 74]], + ]; + + /** + * @var array + */ + protected $QR_PATTERN_POSITION_TABLE = [ + [], + [6, 18], + [6, 22], + [6, 26], + [6, 30], + [6, 34], + [6, 22, 38], + [6, 24, 42], + [6, 26, 46], + [6, 28, 50], + [6, 30, 54], + [6, 32, 58], + [6, 34, 62], + [6, 26, 46, 66], + [6, 26, 48, 70], + [6, 26, 50, 74], + [6, 30, 54, 78], + [6, 30, 56, 82], + [6, 30, 58, 86], + [6, 34, 62, 90], + [6, 28, 50, 72, 94], + [6, 26, 50, 74, 98], + [6, 30, 54, 78, 102], + [6, 28, 54, 80, 106], + [6, 32, 58, 84, 110], + [6, 30, 58, 86, 114], + [6, 34, 62, 90, 118], + [6, 26, 50, 74, 98, 122], + [6, 30, 54, 78, 102, 126], + [6, 26, 52, 78, 104, 130], + [6, 30, 56, 82, 108, 134], + [6, 34, 60, 86, 112, 138], + [6, 30, 58, 86, 114, 142], + [6, 34, 62, 90, 118, 146], + [6, 30, 54, 78, 102, 126, 150], + [6, 24, 50, 76, 102, 128, 154], + [6, 28, 54, 80, 106, 132, 158], + [6, 32, 58, 84, 110, 136, 162], + [6, 26, 54, 82, 110, 138, 166], + [6, 30, 58, 86, 114, 142, 170], + ]; + + /** + * @var \codemasher\QRCode\Math + */ + protected $math; + + /** + * Util constructor. + */ + public function __construct(){ + $this->math = new Math; + } + + /** + * @param int $typeNumber + * + * @return int + */ + public function getPatternPosition($typeNumber){ + return $this->QR_PATTERN_POSITION_TABLE[$typeNumber - 1]; + } + + /** + * @param int $typeNumber + * @param int $mode + * @param int $errorCorrectLevel + * + * @return mixed + * @throws \codemasher\QRCode\QRCodeException + */ + public function getMaxLength($typeNumber, $mode, $errorCorrectLevel){ + $_type = $typeNumber - 1; + + switch($errorCorrectLevel){ + case QRConst::ERROR_CORRECT_LEVEL_L: + $_err = 0; + break; + case QRConst::ERROR_CORRECT_LEVEL_M: + $_err = 1; + break; + case QRConst::ERROR_CORRECT_LEVEL_Q: + $_err = 2; + break; + case QRConst::ERROR_CORRECT_LEVEL_H: + $_err = 3; + break; + default: + throw new QRCodeException('$_err: '.$errorCorrectLevel); + } + + switch($mode){ + case QRConst::MODE_NUMBER: + $_mode = 0; + break; + case QRConst::MODE_ALPHA_NUM: + $_mode = 1; + break; + case QRConst::MODE_8BIT_BYTE: + $_mode = 2; + break; + case QRConst::MODE_KANJI: + $_mode = 3; + break; + default : + throw new QRCodeException('$_mode: '.$mode); + } + + return $this->QR_MAX_LENGTH[$_type][$_err][$_mode]; + } + + /** + * @param $errorCorrectLength + * + * @return \codemasher\QRCode\Polynomial + */ + public function getErrorCorrectPolynomial($errorCorrectLength){ + $a = new Polynomial([1]); + + for($i = 0; $i < $errorCorrectLength; $i++){ + $a = $a->multiply(new Polynomial([1, $this->math->gexp($i)])); + } + + return $a; + } + + /** + * @param $maskPattern + * @param $i + * @param $j + * + * @return bool + * @throws \codemasher\QRCode\QRCodeException + */ + public function getMask($maskPattern, $i, $j){ + + switch($maskPattern){ + case QRConst::MASK_PATTERN000: + return ($i + $j) % 2 === 0; + case QRConst::MASK_PATTERN001: + return $i % 2 === 0; + case QRConst::MASK_PATTERN010: + return $j % 3 === 0; + case QRConst::MASK_PATTERN011: + return ($i + $j) % 3 === 0; + case QRConst::MASK_PATTERN100: + return (floor($i / 2) + floor($j / 3)) % 2 === 0; + case QRConst::MASK_PATTERN101: + return ($i * $j) % 2 + ($i * $j) % 3 === 0; + case QRConst::MASK_PATTERN110: + return (($i * $j) % 2 + ($i * $j) % 3) % 2 === 0; + case QRConst::MASK_PATTERN111: + return (($i * $j) % 3 + ($i + $j) % 2) % 2 === 0; + default : + throw new QRCodeException('mask: '.$maskPattern); + } + } + + /** + * @param \codemasher\QRCode\QRCode $qrCode + * + * @return float|int + */ + public function getLostPoint(QRCode $qrCode){ + $moduleCount = $qrCode->getModuleCount(); + + $lostPoint = 0; + + // LEVEL1 + + for($row = 0; $row < $moduleCount; $row++){ + for($col = 0; $col < $moduleCount; $col++){ + $sameCount = 0; + $dark = $qrCode->isDark($row, $col); + + for($r = -1; $r <= 1; $r++){ + if($row + $r < 0 || $moduleCount <= $row + $r){ + continue; + } + + for($c = -1; $c <= 1; $c++){ + + if($col + $c < 0 || $moduleCount <= $col + $c){ + continue; + } + + if($r == 0 && $c == 0){ + continue; + } + + if($dark == $qrCode->isDark($row + $r, $col + $c)){ + $sameCount++; + } + } + } + + if($sameCount > 5){ + $lostPoint += (3 + $sameCount - 5); + } + } + } + + // LEVEL2 + + for($row = 0; $row < $moduleCount - 1; $row++){ + for($col = 0; $col < $moduleCount - 1; $col++){ + $count = 0; + + if($qrCode->isDark($row, $col)){ + $count++; + } + + if($qrCode->isDark($row + 1, $col)){ + $count++; + } + + if($qrCode->isDark($row, $col + 1)){ + $count++; + } + + if($qrCode->isDark($row + 1, $col + 1)){ + $count++; + } + + if($count === 0 || $count === 4){ + $lostPoint += 3; + } + } + } + + // LEVEL3 + + for($row = 0; $row < $moduleCount; $row++){ + for($col = 0; $col < $moduleCount - 6; $col++){ + if($qrCode->isDark($row, $col) + && !$qrCode->isDark($row, $col + 1) + && $qrCode->isDark($row, $col + 2) + && $qrCode->isDark($row, $col + 3) + && $qrCode->isDark($row, $col + 4) + && !$qrCode->isDark($row, $col + 5) + && $qrCode->isDark($row, $col + 6) + ){ + $lostPoint += 40; + } + } + } + + for($col = 0; $col < $moduleCount; $col++){ + for($row = 0; $row < $moduleCount - 6; $row++){ + if($qrCode->isDark($row, $col) + && !$qrCode->isDark($row + 1, $col) + && $qrCode->isDark($row + 2, $col) + && $qrCode->isDark($row + 3, $col) + && $qrCode->isDark($row + 4, $col) + && !$qrCode->isDark($row + 5, $col) + && $qrCode->isDark($row + 6, $col) + ){ + $lostPoint += 40; + } + } + } + + // LEVEL4 + + $darkCount = 0; + for($col = 0; $col < $moduleCount; $col++){ + for($row = 0; $row < $moduleCount; $row++){ + if($qrCode->isDark($row, $col)){ + $darkCount++; + } + } + } + + $ratio = abs(100 * $darkCount / $moduleCount / $moduleCount - 50) / 5; + $lostPoint += $ratio * 10; + + return $lostPoint; + } + + /** + * @param $s + * + * @return int + */ + public function getMode($s){ + if($this->isAlphaNum($s)){ + if($this->isNumber($s)){ + return QRConst::MODE_NUMBER; + } + return QRConst::MODE_ALPHA_NUM; + } + else if($this->isKanji($s)){ + return QRConst::MODE_KANJI; + } + else{ + return QRConst::MODE_8BIT_BYTE; + } + } + + /** + * @param $s + * + * @return bool + */ + public function isNumber($s){ + for($i = 0; $i < strlen($s); $i++){ + $c = ord($s[$i]); + + if(!($this->toCharCode('0') <= $c && $c <= $this->toCharCode('9'))){ + return false; + } + } + + return true; + } + + /** + * @param $s + * + * @return bool + */ + public function isAlphaNum($s){ + for($i = 0; $i < strlen($s); $i++){ + $c = ord($s[$i]); + + if(!($this->toCharCode('0') <= $c && $c <= $this->toCharCode('9')) + && !($this->toCharCode('A') <= $c && $c <= $this->toCharCode('Z')) + && strpos(' $%*+-./:', $s[$i]) === false + ){ + return false; + } + } + + return true; + } + + /** + * @param $s + * + * @return bool + */ + public function isKanji($s){ + $data = $s; + $i = 0; + + while($i + 1 < strlen($data)){ + $c = ((0xff&ord($data[$i])) << 8)|(0xff&ord($data[$i + 1])); + + if(!(0x8140 <= $c && $c <= 0x9FFC) && !(0xE040 <= $c && $c <= 0xEBBF)){ + return false; + } + + $i += 2; + } + + if($i < strlen($data)){ + return false; + } + + return true; + } + + /** + * @param $s + * + * @return int + */ + public function toCharCode($s){ + return ord($s[0]); + } + + /** + * @param $data + * + * @return int + */ + public function getBCHTypeInfo($data){ + $d = $data << 10; + + while($this->getBCHDigit($d) - $this->getBCHDigit(self::QR_G15) >= 0){ + $d ^= (self::QR_G15 << ($this->getBCHDigit($d) - $this->getBCHDigit(self::QR_G15))); + } + + return (($data << 10)|$d)^self::QR_G15_MASK; + } + + /** + * @param $data + * + * @return int + */ + public function getBCHTypeNumber($data){ + $d = $data << 12; + + while($this->getBCHDigit($d) - $this->getBCHDigit(self::QR_G18) >= 0){ + $d ^= (self::QR_G18 << ($this->getBCHDigit($d) - $this->getBCHDigit(self::QR_G18))); + } + + return ($data << 12)|$d; + } + + /** + * @param $data + * + * @return int + */ + public function getBCHDigit($data){ + $digit = 0; + + while($data != 0){ + $digit++; + $data >>= 1; + } + + return $digit; + } + +}