From e21a7da6868c575bfbbf7de9fa3257c90d5a7255 Mon Sep 17 00:00:00 2001 From: smiley Date: Tue, 8 Dec 2015 04:02:13 +0100 Subject: [PATCH] :godmode: --- examples/custom.php | 23 +- src/BitBuffer.php | 33 +- src/Data/AlphaNum.php | 49 +-- src/Data/Byte.php | 12 +- src/Data/Kanji.php | 19 +- src/Data/Number.php | 51 +-- src/Data/QRDataBase.php | 37 +- src/Data/QRDataInterface.php | 12 +- src/Polynomial.php | 82 ++--- src/QRCode.php | 681 +++++++++++++++-------------------- src/QRCodeException.php | 4 +- src/QRConst.php | 188 +++++++++- src/RSBlock.php | 144 -------- src/Util.php | 226 ++++++------ 14 files changed, 678 insertions(+), 883 deletions(-) delete mode 100644 src/RSBlock.php diff --git a/examples/custom.php b/examples/custom.php index 3732219d8..2123d68f9 100644 --- a/examples/custom.php +++ b/examples/custom.php @@ -2,21 +2,28 @@ require_once '../vendor/autoload.php'; -use codemasher\QRCode\QRCode; -use codemasher\QRCode\QRConst; +use chillerlan\QRCode\QRCode; +use chillerlan\QRCode\QRConst; $starttime = microtime(true); +$qrcode = (new QRCode) + ->setErrorCorrectLevel(QRConst::ERROR_CORRECT_LEVEL_M) + ->setQRType(QRConst::TYPE_05) +; + // google authenticator // https://chart.googleapis.com/chart?chs=200x200&chld=M%7C0&cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%3Fsecret%3DB3JX4VCVJDVNXNZ5%26issuer%3Dchillerlan.net +$qrcode + ->setData('otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net') + ->getRawData() +; -$qrcode = new QRCode('otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', QRConst::ERROR_CORRECT_LEVEL_M, 5); - -for($row = 0; $row < $qrcode->moduleCount; $row++){ - for($col = 0; $col < $qrcode->moduleCount; $col++){ - echo (bool)$qrcode->modules[$row][$col] ? '#' : ' '; +for($row = 0; $row < $qrcode->pixelCount; $row++){ + for($col = 0; $col < $qrcode->pixelCount; $col++){ + echo $qrcode->matrix[$row][$col] ? '*' : ' '; } echo PHP_EOL; } -echo 'QRCode '.round((microtime(true)-$starttime), 5).PHP_EOL; +echo 'QRCode: '.round((microtime(true)-$starttime), 5).PHP_EOL; diff --git a/src/BitBuffer.php b/src/BitBuffer.php index a62d3a421..c81b4c1d2 100644 --- a/src/BitBuffer.php +++ b/src/BitBuffer.php @@ -1,18 +1,19 @@ * @copyright 2015 Smiley * @license MIT */ -namespace codemasher\QRCode; +namespace chillerlan\QRCode; /** - * Class BitBuffer + * */ class BitBuffer{ @@ -27,35 +28,29 @@ class BitBuffer{ public $length = 0; /** - * @return string + * */ -/* public function __toString(){ - $buffer = ''; - - for($i = 0; $i < $this->length; $i++){ - $buffer .= (string)(int)(($this->buffer[(int)floor($i / 8)] >> (7 - $i % 8))&1) === 1; - } - - return $buffer; + public function clear(){ + $this->buffer = []; + $this->length = 0; } -*/ + /** - * @param $num - * @param $length + * @param int $num + * @param int $length * * @return $this */ public function put($num, $length){ for($i = 0; $i < $length; $i++){ - $this->putBit((($num >> ($length - $i - 1))&1) === 1); + $this->putBit(($num >> ($length - $i - 1))&1 === 1); } - return $this; } /** - * @param $bit + * @param bool $bit * * @return $this */ @@ -71,8 +66,6 @@ class BitBuffer{ } $this->length++; - - return $this; } } diff --git a/src/Data/AlphaNum.php b/src/Data/AlphaNum.php index 39677c5b5..1c9a2ce69 100644 --- a/src/Data/AlphaNum.php +++ b/src/Data/AlphaNum.php @@ -4,17 +4,17 @@ * * @filesource AlphaNum.php * @created 25.11.2015 - * @package codemasher\QRCode\Data + * @package chillerlan\QRCode\Data * @author Smiley * @copyright 2015 Smiley * @license MIT */ -namespace codemasher\QRCode\Data; +namespace chillerlan\QRCode\Data; -use codemasher\QRCode\BitBuffer; -use codemasher\QRCode\QRCodeException; -use codemasher\QRCode\QRConst; +use chillerlan\QRCode\BitBuffer; +use chillerlan\QRCode\QRConst; +use chillerlan\QRCode\Util; /** * @@ -22,7 +22,7 @@ use codemasher\QRCode\QRConst; class AlphaNum extends QRDataBase implements QRDataInterface{ /** - * @var + * @var int */ public $mode = QRConst::MODE_ALPHANUM; @@ -32,47 +32,18 @@ class AlphaNum extends QRDataBase implements QRDataInterface{ protected $lengthBits = [9, 11, 13]; /** - * @param $buffer + * @param \chillerlan\QRCode\BitBuffer $buffer */ public function write(BitBuffer &$buffer){ - $i = 0; + while($i + 1 < $this->dataLength){ - $buffer->put($this->getCode(ord($this->data[$i])) * 45 + $this->getCode(ord($this->data[$i + 1])), 11); + $buffer->put(Util::getCharCode($this->data[$i]) * 45 + Util::getCharCode($this->data[$i + 1]), 11); $i += 2; } if($i < $this->dataLength){ - $buffer->put($this->getCode(ord($this->data[$i])), 6); - } - - } - - /** - * @param $c - * - * @return int - * @throws \codemasher\QRCode\QRCodeException - */ - protected function getCode($c){ - - switch(true){ - case ord('0') <= $c && $c <= ord('9'): return $c - ord('0'); - case ord('A') <= $c && $c <= ord('Z'): return $c - ord('A') + 10; - default: - switch($c){ - case ord(' '): return 36; - case ord('$'): return 37; - case ord('%'): return 38; - case ord('*'): return 39; - case ord('+'): return 40; - case ord('-'): return 41; - case ord('.'): return 42; - case ord('/'): return 43; - case ord(':'): return 44; - default: - throw new QRCodeException('illegal char: '.$c); - } + $buffer->put(Util::getCharCode($this->data[$i]), 6); } } diff --git a/src/Data/Byte.php b/src/Data/Byte.php index 7bec67979..97f758f39 100644 --- a/src/Data/Byte.php +++ b/src/Data/Byte.php @@ -4,16 +4,16 @@ * * @filesource Byte.php * @created 25.11.2015 - * @package codemasher\QRCode\Data + * @package chillerlan\QRCode\Data * @author Smiley * @copyright 2015 Smiley * @license MIT */ -namespace codemasher\QRCode\Data; +namespace chillerlan\QRCode\Data; -use codemasher\QRCode\BitBuffer; -use codemasher\QRCode\QRConst; +use chillerlan\QRCode\BitBuffer; +use chillerlan\QRCode\QRConst; /** * @@ -21,7 +21,7 @@ use codemasher\QRCode\QRConst; class Byte extends QRDataBase implements QRDataInterface{ /** - * @var + * @var int */ public $mode = QRConst::MODE_BYTE; @@ -31,7 +31,7 @@ class Byte extends QRDataBase implements QRDataInterface{ protected $lengthBits = [8, 16, 16]; /** - * @param $buffer + * @param \chillerlan\QRCode\BitBuffer $buffer */ public function write(BitBuffer &$buffer){ for($i = 0; $i < $this->dataLength; $i++){ diff --git a/src/Data/Kanji.php b/src/Data/Kanji.php index d921271e5..a4fb7829b 100644 --- a/src/Data/Kanji.php +++ b/src/Data/Kanji.php @@ -4,17 +4,17 @@ * * @filesource Kanji.php * @created 25.11.2015 - * @package codemasher\QRCode\Data + * @package chillerlan\QRCode\Data * @author Smiley * @copyright 2015 Smiley * @license MIT */ -namespace codemasher\QRCode\Data; +namespace chillerlan\QRCode\Data; -use codemasher\QRCode\BitBuffer; -use codemasher\QRCode\QRCodeException; -use codemasher\QRCode\QRConst; +use chillerlan\QRCode\BitBuffer; +use chillerlan\QRCode\QRCodeException; +use chillerlan\QRCode\QRConst; /** * @@ -22,7 +22,7 @@ use codemasher\QRCode\QRConst; class Kanji extends QRDataBase implements QRDataInterface{ /** - * @var + * @var int */ public $mode = QRConst::MODE_KANJI; @@ -32,9 +32,9 @@ class Kanji extends QRDataBase implements QRDataInterface{ protected $lengthBits = [8, 10, 12]; /** - * @param $buffer + * @param \chillerlan\QRCode\BitBuffer $buffer * - * @throws \codemasher\QRCode\QRCodeException + * @throws \chillerlan\QRCode\QRCodeException */ public function write(BitBuffer &$buffer){ @@ -52,8 +52,7 @@ class Kanji extends QRDataBase implements QRDataInterface{ throw new QRCodeException('illegal char at '.($i + 1).' ('.$c.')'); } - $c = (($c >> 8)&0xff) * 0xC0 + ($c&0xff); - $buffer->put($c, 13); + $buffer->put((($c >> 8)&0xff) * 0xC0 + ($c&0xff), 13); $i += 2; } diff --git a/src/Data/Number.php b/src/Data/Number.php index a547e74e1..5693d41b0 100644 --- a/src/Data/Number.php +++ b/src/Data/Number.php @@ -10,11 +10,11 @@ * @license MIT */ -namespace codemasher\QRCode\Data; +namespace chillerlan\QRCode\Data; -use codemasher\QRCode\BitBuffer; -use codemasher\QRCode\QRCodeException; -use codemasher\QRCode\QRConst; +use chillerlan\QRCode\BitBuffer; +use chillerlan\QRCode\QRConst; +use chillerlan\QRCode\Util; /** * @@ -32,60 +32,27 @@ class Number extends QRDataBase implements QRDataInterface{ protected $lengthBits = [10, 12, 14]; /** - * @param $buffer - * - * @return $this + * @param \chillerlan\QRCode\BitBuffer $buffer */ public function write(BitBuffer &$buffer){ - $i = 0; + while($i + 2 < $this->dataLength){ - $num = $this->parseInt(substr($this->data, $i, 3)); - $buffer->put($num, 10); + $buffer->put(Util::parseInt(substr($this->data, $i, 3)), 10); $i += 3; } if($i < $this->dataLength){ if($this->dataLength - $i === 1){ - $num = $this->parseInt(substr($this->data, $i, $i + 1)); - $buffer->put($num, 4); + $buffer->put(Util::parseInt(substr($this->data, $i, $i + 1)), 4); } else if($this->dataLength - $i === 2){ - $num = $this->parseInt(substr($this->data, $i, $i + 2)); - $buffer->put($num, 7); + $buffer->put(Util::parseInt(substr($this->data, $i, $i + 2)), 7); } } - return $this; - } - - /** - * @param string $string - * - * @return int - * @throws \codemasher\QRCode\QRCodeException - */ - protected function parseInt($string){ - - $num = 0; - $len = strlen($string); - for($i = 0; $i < $len; $i++){ - $c = ord($string[$i]); - - $ord0 = ord('0'); - if(ord('0') <= $c && $c <= ord('9')){ - $c = $c - $ord0; - } - else{ - throw new QRCodeException('illegal char: '.$c); - } - - $num = $num * 10 + $c; - } - - return $num; } } diff --git a/src/Data/QRDataBase.php b/src/Data/QRDataBase.php index 453a30fca..47b910f7b 100644 --- a/src/Data/QRDataBase.php +++ b/src/Data/QRDataBase.php @@ -4,26 +4,20 @@ * * @filesource QRDataBase.php * @created 25.11.2015 - * @package codemasher\QRCode\Data + * @package chillerlan\QRCode\Data * @author Smiley * @copyright 2015 Smiley * @license MIT */ -namespace codemasher\QRCode\Data; +namespace chillerlan\QRCode\Data; -use codemasher\QRCode\BitBuffer; -use codemasher\QRCode\QRCodeException; +use chillerlan\QRCode\QRCodeException; /** * */ -class QRDataBase implements QRDataInterface{ - - /** - * @var - */ - public $mode; +class QRDataBase{ /** * @var string @@ -43,7 +37,7 @@ class QRDataBase implements QRDataInterface{ /** * QRDataBase constructor. * - * @param $data + * @param string $data */ public function __construct($data){ $this->data = $data; @@ -51,32 +45,21 @@ class QRDataBase implements QRDataInterface{ } /** - * @param $type + * @param int $type * * @return int - * @throws \codemasher\QRCode\QRCodeException + * @throws \chillerlan\QRCode\QRCodeException */ public function getLengthInBits($type){ switch(true){ - case $type >= 1 && $type <= 9: return $this->lengthBits[0]; // 1 - 9 - case $type <= 26 : return $this->lengthBits[1]; // 10 - 26 - case $type <= 40 : return $this->lengthBits[2]; // 27 - 40 + case $type >= 1 && $type <= 9: return $this->lengthBits[0]; // 1 - 9 + case $type <= 26 : return $this->lengthBits[1]; // 10 - 26 + case $type <= 40 : return $this->lengthBits[2]; // 27 - 40 default: throw new QRCodeException('$type: '.$type); } } - /** - * @todo Implement write() method. - * - * @param \codemasher\QRCode\BitBuffer $buffer - * - * @throws \codemasher\QRCode\QRCodeException - */ - public function write(BitBuffer &$buffer){ - throw new QRCodeException('Method should be implemented in child classes'); - } - } diff --git a/src/Data/QRDataInterface.php b/src/Data/QRDataInterface.php index 66f13514c..2129fe3f5 100644 --- a/src/Data/QRDataInterface.php +++ b/src/Data/QRDataInterface.php @@ -4,15 +4,15 @@ * * @filesource QRDataInterface.php * @created 01.12.2015 - * @package codemasher\QRCode\Data + * @package chillerlan\QRCode\Data * @author Smiley * @copyright 2015 Smiley * @license MIT */ -namespace codemasher\QRCode\Data; +namespace chillerlan\QRCode\Data; -use codemasher\QRCode\BitBuffer; +use chillerlan\QRCode\BitBuffer; /** * @@ -20,7 +20,7 @@ use codemasher\QRCode\BitBuffer; interface QRDataInterface{ /** - * @param \codemasher\QRCode\BitBuffer $buffer + * @param \chillerlan\QRCode\BitBuffer $buffer */ public function write(BitBuffer &$buffer); @@ -28,8 +28,8 @@ interface QRDataInterface{ * @param $type * * @return int - * @throws \codemasher\QRCode\QRCodeException + * @throws \chillerlan\QRCode\QRCodeException */ public function getLengthInBits($type); -} \ No newline at end of file +} diff --git a/src/Polynomial.php b/src/Polynomial.php index 01cd40698..f42428d53 100644 --- a/src/Polynomial.php +++ b/src/Polynomial.php @@ -1,18 +1,19 @@ * @copyright 2015 Smiley * @license MIT */ -namespace codemasher\QRCode; +namespace chillerlan\QRCode; /** - * Class Polynomial + * */ class Polynomial{ @@ -65,10 +66,9 @@ class Polynomial{ } /** - * @return $this + * */ protected function setTables(){ - $this->EXP_TABLE = $this->LOG_TABLE = array_fill(0, 256, 0); for($i = 0; $i < 8; $i++){ @@ -83,86 +83,46 @@ class Polynomial{ $this->LOG_TABLE[$this->EXP_TABLE[$i]] = $i; } - return $this; } - /** - * @return string - */ -/* public function __toString(){ - $buffer = ''; - - foreach($this->num as $i => $value){ - if($i > 0){ - $buffer .= ','; - } - $buffer .= $value; - } - - return $buffer; - } -*/ - /** - * @return string - */ -/* public function toLogString(){ - $buffer = ''; - - foreach($this->num as $i => $value){ - if($i > 0){ - $buffer .= ','; - } - $buffer .= $this->glog($value); - } - - return $buffer; - } -*/ /** * @param array $e - * - * @return $this */ public function multiply(array $e){ + $n = array_fill(0, count($this->num) + count($e) - 1, 0); - $num = array_fill(0, count($this->num) + count($e) - 1, 0); - foreach($this->num as $i => $vi){ - foreach($e as $j => $vj){ - $num[$i + $j] ^= $this->gexp($this->glog($vi) + $this->glog($vj)); + foreach($this->num as $i => &$vi){ + foreach($e as $j => &$vj){ + $n[$i + $j] ^= $this->gexp($this->glog($vi) + $this->glog($vj)); } } - $this->setNum($num); - - return $this; + $this->setNum($n); } /** * @param array $e - * - * @return $this */ - public function mod($e){ + public function mod(array $e){ + $n = $this->num; - if(count($this->num) - count($e) < 0){ - return $this; + if(count($n) - count($e) < 0){ + return; } - $ratio = $this->glog($this->num[0]) - $this->glog($e[0]); - foreach($e as $i => $value){ - $this->num[$i] ^= $this->gexp($this->glog($e[$i]) + $ratio); + $ratio = $this->glog($n[0]) - $this->glog($e[0]); + foreach($e as $i => &$v){ + $n[$i] ^= $this->gexp($this->glog($v) + $ratio); } - $this->setNum($this->num)->mod($e); - - return $this->num; + $this->setNum($n)->mod($e); } /** * @param int $n * - * @return mixed - * @throws \codemasher\QRCode\QRCodeException + * @return int + * @throws \chillerlan\QRCode\QRCodeException */ public function glog($n){ @@ -176,7 +136,7 @@ class Polynomial{ /** * @param int $n * - * @return mixed + * @return int */ public function gexp($n){ diff --git a/src/QRCode.php b/src/QRCode.php index 55feba592..03f98b770 100644 --- a/src/QRCode.php +++ b/src/QRCode.php @@ -4,33 +4,35 @@ * * @filesource QRCode.php * @created 26.11.2015 - * @package codemasher\QRCode + * @package chillerlan\QRCode * @author Smiley * @copyright 2015 Smiley * @license MIT */ -namespace codemasher\QRCode; +namespace chillerlan\QRCode; -use codemasher\QRCode\Data\QRDataBase; +use chillerlan\QRCode\Data\QRDataInterface; /** * @link https://github.com/kazuhikoarase/qrcode-generator/tree/master/php */ class QRCode{ - const QR_PAD0 = 0xEC; - const QR_PAD1 = 0x11; - /** * @var int */ - public $moduleCount; + public $pixelCount; /** * @var array */ - public $modules; + public $matrix; + + /** + * @var int + */ + protected $mode; /** * @var int @@ -43,146 +45,69 @@ class QRCode{ protected $errorCorrectLevel; /** - * @var array -> \codemasher\QRCode\Data\QRDataInterface + * @var \chillerlan\QRCode\Data\QRDataInterface */ - protected $qrDataList = []; + protected $qrDataInterface; /** - * @var array - */ - protected $rsBlockList = []; - - /** - * @var - */ - protected $data; - - /** - * @var int - */ - protected $mode; - - /** - * @var array - */ - protected $ERROR_CORRECT_LEVEL = [ - QRConst::ERROR_CORRECT_LEVEL_L, - QRConst::ERROR_CORRECT_LEVEL_M, - QRConst::ERROR_CORRECT_LEVEL_Q, - QRConst::ERROR_CORRECT_LEVEL_H, - ]; - - /** - * @var array - */ - protected $qrDataInterface = [ - QRConst::MODE_ALPHANUM => '\\codemasher\\QRCode\\Data\\AlphaNum', - QRConst::MODE_BYTE => '\\codemasher\\QRCode\\Data\\Byte', - QRConst::MODE_KANJI => '\\codemasher\\QRCode\\Data\\Kanji', - QRConst::MODE_NUMBER => '\\codemasher\\QRCode\\Data\\Number', - ]; - - /** - * @var \codemasher\QRCode\Util - */ - protected $util; - - /** - * @var \codemasher\QRCode\RSBlock - */ - protected $rsBlock; - - /** - * @var \codemasher\QRCode\BitBuffer + * @var \chillerlan\QRCode\BitBuffer */ protected $bitBuffer; /** - * @todo WIP - * * QRCode constructor. * * @param string $data * @param int $errorCorrectLevel * @param int $typeNumber * - * @throws \codemasher\QRCode\QRCodeException + * @throws \chillerlan\QRCode\QRCodeException */ public function __construct($data = '', $errorCorrectLevel = QRConst::ERROR_CORRECT_LEVEL_M, $typeNumber = null){ - $this->util = new Util; - $this->rsBlock = new RSBlock; $this->bitBuffer = new BitBuffer; - $this->setErrorCorrectLevel($errorCorrectLevel)->setTypeNumber($typeNumber); - if(!empty($data)){ - $this->getMinimumQRCode($data); + $this->getQRCode($data, $errorCorrectLevel, $typeNumber); } } /** - * @todo WIP - * - * @param $data - * - * @return \codemasher\QRCode\QRCode - * @throws \codemasher\QRCode\QRCodeException - */ - public function getMinimumQRCode($data){ - $mode = $this->util->getMode($data); - $this->addData($data, $mode); - - /** @var \codemasher\QRCode\Data\QRDataBase $qrData */ - $qrData = $this->qrDataList[0]; - $length = $qrData->mode === QRConst::MODE_KANJI ? floor($qrData->dataLength / 2) : $qrData->dataLength; - - for($typeNumber = 1; $typeNumber <= 10; $typeNumber++){ - if($length <= $this->util->getMaxLength($typeNumber, $mode, $this->errorCorrectLevel)){ - $this->typeNumber = $typeNumber; - break; - } - } - - $this->make(); - - return $this; - } - - /** - * @todo WIP - * * @param string $data * - * @param null $mode - * * @return $this - * @throws \codemasher\QRCode\QRCodeException + * @throws \chillerlan\QRCode\QRCodeException */ - public function addData($data, $mode = null){ - if($mode === null){ - $mode = $this->util->getMode($data); + public function setData($data){ + $data = trim((string)$data); + + if(empty($data)){ + throw new QRCodeException('No data given.'); } - if(!isset($this->qrDataInterface[$mode])){ - throw new QRCodeException('mode: '.$mode); - } + $this->mode = Util::getMode($data); - $this->qrDataList[] = new $this->qrDataInterface[$mode]($data); + $qrDataInterface = __NAMESPACE__.'\\Data\\'.[ + QRConst::MODE_ALPHANUM => 'AlphaNum', + QRConst::MODE_BYTE => 'Byte', + QRConst::MODE_KANJI => 'Kanji', + QRConst::MODE_NUMBER => 'Number', + ][$this->mode]; + + $this->qrDataInterface = new $qrDataInterface($data); return $this; } - /** - * @param $errorCorrectLevel + * @param int $errorCorrectLevel * * @return $this - * @throws \codemasher\QRCode\QRCodeException + * @throws \chillerlan\QRCode\QRCodeException */ public function setErrorCorrectLevel($errorCorrectLevel){ - if(!in_array($errorCorrectLevel, $this->ERROR_CORRECT_LEVEL)){ + if(!array_key_exists($errorCorrectLevel, QRConst::RSBLOCK)){ throw new QRCodeException('Invalid error correct level: '.$errorCorrectLevel); } @@ -195,68 +120,104 @@ class QRCode{ * @param int $typeNumber * * @return $this - * @throws \codemasher\QRCode\QRCodeException + * @throws \chillerlan\QRCode\QRCodeException */ - public function setTypeNumber($typeNumber){ - $typeNumber = intval($typeNumber); + public function setQRType($typeNumber){ + $this->typeNumber = intval($typeNumber); + + if($this->typeNumber < 1 || $this->typeNumber > 10){ + + $length = $this->qrDataInterface->mode === QRConst::MODE_KANJI ? floor($this->qrDataInterface->dataLength / 2) : $this->qrDataInterface->dataLength; + + for($type = 1; $type <= 10; $type++){ + if($length <= Util::getMaxLength($type, $this->mode, $this->errorCorrectLevel)){ + $this->typeNumber = $type; + + return $this; + } + } - if($typeNumber < 1 || $typeNumber > 10){ - throw new QRCodeException('Invalid type number: '.$typeNumber); } - $this->typeNumber = $typeNumber; - return $this; } /** + * @param string $data + * @param int $errorCorrectLevel + * @param int $typeNumber * + * @return $this + * @throws \chillerlan\QRCode\QRCodeException */ - public function make(){ + public function getQRCode($data, $errorCorrectLevel = QRConst::ERROR_CORRECT_LEVEL_M, $typeNumber = null){ + + $this + ->setData($data) + ->setErrorCorrectLevel($errorCorrectLevel) + ->setQRType($typeNumber) + ->getRawData() + ; + + return $this; + } + + /** + * @return $this + */ + public function getRawData(){ + + // getLostPoint $minLostPoint = 0; $pattern = 0; for($i = 0; $i < 8; $i++){ - $this->makeImpl(true, $i); + $this->getMatrix(true, $i); $lostPoint = 0; // LEVEL1 - for($row = 0; $row < $this->moduleCount; $row++){ - for($col = 0; $col < $this->moduleCount; $col++){ + for($row = 0; $row < $this->pixelCount; $row++){ + for($col = 0; $col < $this->pixelCount; $col++){ $sameCount = 0; - $dark = $this->modules[$row][$col]; + $dark = $this->matrix[$row][$col]; for($r = -1; $r <= 1; $r++){ - if($row + $r < 0 || $this->moduleCount <= $row + $r){ + + if($row + $r < 0 || $this->pixelCount <= $row + $r){ continue; } for($c = -1; $c <= 1; $c++){ - if(($r === 0 && $c === 0) || ($col + $c < 0 || $this->moduleCount <= $col + $c)){ + if(($r === 0 && $c === 0) || ($col + $c < 0 || $this->pixelCount <= $col + $c)){ continue; } - if($this->modules[$row + $r][$col + $c] === $dark){ + if($this->matrix[$row + $r][$col + $c] === $dark){ $sameCount++; } } + } if($sameCount > 5){ $lostPoint += (3 + $sameCount - 5); } + } } // LEVEL2 - for($row = 0; $row < $this->moduleCount - 1; $row++){ - for($col = 0; $col < $this->moduleCount - 1; $col++){ + for($row = 0; $row < $this->pixelCount - 1; $row++){ + for($col = 0; $col < $this->pixelCount - 1; $col++){ $count = 0; - if($this->modules[$row][$col] || $this->modules[$row][$col + 1] - || $this->modules[$row + 1][$col] || $this->modules[$row + 1][$col + 1] + if( + $this->matrix[$row ][$col ] + || $this->matrix[$row ][$col + 1] + || $this->matrix[$row + 1][$col ] + || $this->matrix[$row + 1][$col + 1] ){ $count++; } @@ -269,30 +230,32 @@ class QRCode{ } // LEVEL3 - for($row = 0; $row < $this->moduleCount; $row++){ - for($col = 0; $col < $this->moduleCount - 6; $col++){ - if($this->modules[$row][$col] - && !$this->modules[$row][$col + 1] - && $this->modules[$row][$col + 2] - && $this->modules[$row][$col + 3] - && $this->modules[$row][$col + 4] - && !$this->modules[$row][$col + 5] - && $this->modules[$row][$col + 6] + for($row = 0; $row < $this->pixelCount; $row++){ + for($col = 0; $col < $this->pixelCount - 6; $col++){ + if( + $this->matrix[$row][$col ] + && !$this->matrix[$row][$col + 1] + && $this->matrix[$row][$col + 2] + && $this->matrix[$row][$col + 3] + && $this->matrix[$row][$col + 4] + && !$this->matrix[$row][$col + 5] + && $this->matrix[$row][$col + 6] ){ $lostPoint += 40; } } } - for($col = 0; $col < $this->moduleCount; $col++){ - for($row = 0; $row < $this->moduleCount - 6; $row++){ - if($this->modules[$row][$col] - && !$this->modules[$row + 1][$col] - && $this->modules[$row + 2][$col] - && $this->modules[$row + 3][$col] - && $this->modules[$row + 4][$col] - && !$this->modules[$row + 5][$col] - && $this->modules[$row + 6][$col] + for($col = 0; $col < $this->pixelCount; $col++){ + for($row = 0; $row < $this->pixelCount - 6; $row++){ + if( + $this->matrix[$row ][$col] + && !$this->matrix[$row + 1][$col] + && $this->matrix[$row + 2][$col] + && $this->matrix[$row + 3][$col] + && $this->matrix[$row + 4][$col] + && !$this->matrix[$row + 5][$col] + && $this->matrix[$row + 6][$col] ){ $lostPoint += 40; } @@ -301,15 +264,15 @@ class QRCode{ // LEVEL4 $darkCount = 0; - for($col = 0; $col < $this->moduleCount; $col++){ - for($row = 0; $row < $this->moduleCount; $row++){ - if($this->modules[$row][$col]){ + for($col = 0; $col < $this->pixelCount; $col++){ + for($row = 0; $row < $this->pixelCount; $row++){ + if($this->matrix[$row][$col]){ $darkCount++; } } } - $ratio = abs(100 * $darkCount / $this->moduleCount / $this->moduleCount - 50) / 5; + $ratio = abs(100 * $darkCount / $this->pixelCount / $this->pixelCount - 50) / 5; $lostPoint += $ratio * 10; if($i === 0 || $minLostPoint > $lostPoint){ @@ -319,7 +282,7 @@ class QRCode{ } - $this->makeImpl(false, $pattern); + $this->getMatrix(false, $pattern); return $this; } @@ -327,35 +290,74 @@ class QRCode{ /** * @param bool $test * @param int $pattern - * - * @return $this - * @throws \codemasher\QRCode\QRCodeException */ - protected function makeImpl($test, $pattern){ - $this->moduleCount = $this->typeNumber * 4 + 17; - $this->modules = []; + protected function setTypeInfo($test, $pattern){ + $this->setPattern(); + $bits = Util::getBCHTypeInfo(($this->errorCorrectLevel << 3) | $pattern); + + for($i = 0; $i < 15; $i++){ + $mod = !$test && (($bits >> $i) & 1) === 1; + + switch(true){ + case $i < 6:$this->matrix[$i ][8] = $mod; break; + case $i < 8:$this->matrix[$i + 1][8] = $mod; break; + default: + $this->matrix[$this->pixelCount - 15 + $i][8] = $mod; + } + + switch(true){ + case $i < 8:$this->matrix[8][$this->pixelCount - $i - 1] = $mod; break; + case $i < 9:$this->matrix[8][ 15 + 1 - $i - 1] = $mod; break; + default: + $this->matrix[8][15 - $i - 1] = $mod; + } - $nullArray = array_fill(0, $this->moduleCount, null); - for($i = 0; $i < $this->moduleCount; $i++){ - $this->modules[] = $nullArray; } - $this->setupPositionProbePattern(0, 0) - ->setupPositionProbePattern($this->moduleCount - 7, 0) - ->setupPositionProbePattern(0, $this->moduleCount - 7); + $this->matrix[$this->pixelCount - 8][8] = !$test; + } + + /** + * @throws \chillerlan\QRCode\QRCodeException + */ + protected function setPattern(){ + + // setupPositionProbePattern + foreach([[0, 0], [$this->pixelCount - 7, 0], [0, $this->pixelCount - 7]] as $grid){ + $row = $grid[0]; + $col = $grid[1]; + + for($r = -1; $r <= 7; $r++){ + for($c = -1; $c <= 7; $c++){ + + if($row + $r <= -1 || $this->pixelCount <= $row + $r || $col + $c <= -1 || $this->pixelCount <= $col + $c){ + continue; + } + + $this->matrix[$row + $r][$col + $c] = + (0 <= $r && $r <= 6 && ($c === 0 || $c === 6)) + || (0 <= $c && $c <= 6 && ($r === 0 || $r === 6)) + || (2 <= $c && $c <= 4 && 2 <= $r && $r <= 4); + } + } + } // setupPositionAdjustPattern - $pos = $this->util->PATTERN_POSITION[$this->typeNumber - 1]; + $PATTERN_POSITION = QRConst::PATTERN_POSITION; // PHP5 compat + $pos = $PATTERN_POSITION[$this->typeNumber - 1]; foreach($pos as $i => $posI){ foreach($pos as $j => $posJ){ - if($this->modules[$posI][$posJ] !== null){ + + if($this->matrix[$posI][$posJ] !== null){ continue; } for($row = -2; $row <= 2; $row++){ for($col = -2; $col <= 2; $col++){ - $this->modules[$posI + $row][$posJ + $col] = - (bool)$row === -2 || $row === 2 || $col === -2 || $col === 2 || ($row === 0 && $col === 0); + $this->matrix[$posI + $row][$posJ + $col] = + $row === -2 || $row === 2 + || $col === -2 || $col === 2 + ||($row === 0 && $col === 0); } } @@ -363,59 +365,71 @@ class QRCode{ } // setupTimingPattern - for($i = 8; $i < $this->moduleCount - 8; $i++){ - if($this->modules[$i][6] !== null){ + for($i = 8; $i < $this->pixelCount - 8; $i++){ + + if($this->matrix[$i][6] !== null){ continue; } - $this->modules[$i][6] = $this->modules[6][$i] = (bool)$i % 2 === 0; + $this->matrix[$i][6] = $this->matrix[6][$i] = $i % 2 === 0; } + } - $data = ($this->errorCorrectLevel << 3) | $pattern; - $bits = $this->util->getBCHTypeInfo($data); + /** + * @param bool $test + * + * @return $this + */ + protected function setTypeNumber($test){ + $bits = Util::getBCHTypeNumber($this->typeNumber); - for($i = 0; $i < 15; $i++){ - $mod = (bool)!$test && (($bits >> $i) & 1) === 1; - - switch(true){ - case $i < 6: $this->modules[$i][8] = $mod; break; - case $i < 8: $this->modules[$i + 1][8] = $mod; break; - default: - $this->modules[$this->moduleCount - 15 + $i][8] = $mod; - } - - switch(true){ - case $i < 8: $this->modules[8][$this->moduleCount - $i - 1] = $mod; break; - case $i < 9: $this->modules[8][15 - $i - 1 + 1] = $mod; break; - default: - $this->modules[8][15 - $i - 1] = $mod; - } + for($i = 0; $i < 18; $i++){ + $a = (int)floor($i / 3); + $b = $i % 3 + $this->pixelCount - 8 - 3; + $this->matrix[$a][$b] = $this->matrix[$b][$a] = !$test && (($bits >> $i) & 1) === 1; } - $this->modules[$this->moduleCount - 8][8] = !$test; + } + + /** + * @param bool $test + * @param int $pattern + * + * @throws \chillerlan\QRCode\QRCodeException + */ + protected function getMatrix($test, $pattern){ + if($this->typeNumber < 1 || $this->typeNumber > 10){ + throw new QRCodeException('Invalid type number '.$this->typeNumber); + } + + $this->pixelCount = $this->typeNumber * 4 + 17; + $this->matrix = array_fill(0, $this->pixelCount, array_fill(0, $this->pixelCount, null)); + $this->setTypeInfo($test, $pattern); if($this->typeNumber >= 7){ - $bits = $this->util->getBCHTypeNumber($this->typeNumber); - - for($i = 0; $i < 18; $i++){ - $a = (int)floor($i / 3); - $b = $i % 3 + $this->moduleCount - 8 - 3; - - $this->modules[$a][$b] = $this->modules[$b][$a] = (bool)!$test && (($bits >> $i) & 1) === 1; - } + $this->setTypeNumber($test); } - $this->data = $this->createData($this->typeNumber, $this->errorCorrectLevel); + $this->mapData($pattern); + } + /** + * @param int $pattern + * + * @throws \chillerlan\QRCode\QRCodeException + */ + protected function mapData($pattern){ + $this->createData(); + $data = $this->createBytes(); $inc = -1; - $row = $this->moduleCount - 1; + $row = $this->pixelCount - 1; $bitIndex = 7; $byteIndex = 0; - $dataCount = count($this->data); + $dataCount = count($data); - for($col = $this->moduleCount - 1; $col > 0; $col -= 2){ + for($col = $this->pixelCount - 1; $col > 0; $col -= 2){ if($col === 6){ $col--; @@ -423,38 +437,36 @@ class QRCode{ while(true){ for($c = 0; $c < 2; $c++){ + $_col = $col - $c; - if($this->modules[$row][$col - $c] === null){ + if($this->matrix[$row][$_col] === null){ $dark = false; if($byteIndex < $dataCount){ - $dark = (($this->data[$byteIndex] >> $bitIndex) & 1) === 1; + $dark = (($data[$byteIndex] >> $bitIndex) & 1) === 1; } - $_col = $col - $c; + $a = $row + $_col; + $m = $row * $_col; $MASK_PATTERN = [ - QRConst::MASK_PATTERN000 => ($row + $_col) % 2 === 0, - QRConst::MASK_PATTERN001 => $row % 2 === 0, - QRConst::MASK_PATTERN010 => $_col % 3 === 0, - QRConst::MASK_PATTERN011 => ($row + $_col) % 3 === 0, - QRConst::MASK_PATTERN100 => (floor($row / 2) + floor($_col / 3)) % 2 === 0, - QRConst::MASK_PATTERN101 => ($row * $_col) % 2 + ($row * $_col) % 3 === 0, - QRConst::MASK_PATTERN110 => (($row * $_col) % 2 + ($row * $_col) % 3) % 2 === 0, - QRConst::MASK_PATTERN111 => (($row * $_col) % 3 + ($row + $_col) % 2) % 2 === 0, - ]; + QRConst::MASK_PATTERN000 => $a % 2, + QRConst::MASK_PATTERN001 => $row % 2, + QRConst::MASK_PATTERN010 => $_col % 3, + QRConst::MASK_PATTERN011 => $a % 3, + QRConst::MASK_PATTERN100 => (floor($row / 2) + floor($_col / 3)) % 2, + QRConst::MASK_PATTERN101 => $m % 2 + $m % 3, + QRConst::MASK_PATTERN110 => ($m % 2 + $m % 3) % 2, + QRConst::MASK_PATTERN111 => ($m % 3 + $a % 2) % 2, + ][$pattern]; - if(!isset($MASK_PATTERN[$pattern])){ - throw new QRCodeException('mask: '.$pattern); - } - - if($MASK_PATTERN[$pattern]){ + if($MASK_PATTERN === 0){ $dark = !$dark; } - $this->modules[$row][$col - $c] = (bool)$dark; - $bitIndex--; + $this->matrix[$row][$_col] = $dark; + $bitIndex--; if($bitIndex === -1){ $byteIndex++; $bitIndex = 7; @@ -465,82 +477,47 @@ class QRCode{ $row += $inc; - if($row < 0 || $this->moduleCount <= $row){ + if($row < 0 || $this->pixelCount <= $row){ $row -= $inc; $inc = -$inc; break; } + } + } - return $this; } /** - * @param $row - * @param $col - * * @return $this + * @throws \chillerlan\QRCode\QRCodeException */ - protected function setupPositionProbePattern($row, $col){ - $range = range(-1, 7); + protected function createData(){ - foreach($range as $r){ - foreach($range as $c){ - - if($row + $r <= -1 || $this->moduleCount <= $row + $r || $col + $c <= -1 || $this->moduleCount <= $col + $c){ - continue; - } - - $this->modules[$row + $r][$col + $c] = - (0 <= $r && $r <= 6 && ($c === 0 || $c === 6)) - || (0 <= $c && $c <= 6 && ($r == 0 || $r === 6)) - || (2 <= $r && $r <= 4 && 2 <= $c && $c <= 4); - } + if(!isset($this->errorCorrectLevel)){ + throw new QRCodeException('Invalid error correct level '.$this->errorCorrectLevel); } - return $this; - } + $this->bitBuffer->clear(); - /** - * @param $typeNumber - * @param $errorCorrectLevel - * - * @return array - * @throws \codemasher\QRCode\QRCodeException - */ - protected function createData($typeNumber, $errorCorrectLevel){ - $this->bitBuffer->buffer = []; - $this->bitBuffer->length = 0; - $this->rsBlockList = $this->rsBlock->getRSBlocks($typeNumber, $errorCorrectLevel); - $rsBlockCount = count($this->rsBlockList); + $MAX_BITS = QRConst::MAX_BITS; // php5 compat + $MAX_BITS = $MAX_BITS[$this->typeNumber][$this->errorCorrectLevel]; - $totalDataCount = $totalCodeCount = $offset = $maxDcCount = $maxEcCount = $index = 0; - $dcdata = $ecdata = array_fill(0, $rsBlockCount, null); + $this->bitBuffer->put($this->qrDataInterface->mode, 4); + $this->bitBuffer->put( + $this->qrDataInterface->mode === QRConst::MODE_KANJI ? floor($this->qrDataInterface->dataLength / 2) : $this->qrDataInterface->dataLength, + $this->qrDataInterface->getLengthInBits($this->typeNumber) + ); + $this->qrDataInterface->write($this->bitBuffer); - /** @var \codemasher\QRCode\Data\QRDataBase $data */ - foreach($this->qrDataList as &$data){ - $this->bitBuffer - ->put($data->mode, 4) - ->put($data->mode === QRConst::MODE_KANJI ? floor($data->dataLength / 2) : $data->dataLength, $data->getLengthInBits($typeNumber)); - - $data->write($this->bitBuffer); - } - - foreach($this->rsBlockList as &$data){ - $this->rsBlock->totalCount = $data[0]; - $this->rsBlock->dataCount = $data[1]; - - $totalDataCount += $this->rsBlock->dataCount; - } - - if($this->bitBuffer->length > $totalDataCount * 8){ - throw new QRCodeException('code length overflow. ('.$this->bitBuffer->length.'>'.($totalDataCount * 8).')'); + if($this->bitBuffer->length > $MAX_BITS){ + throw new QRCodeException('code length overflow. ('.$this->bitBuffer->length.' > '.$MAX_BITS.'bit)'); } // end code. - if($this->bitBuffer->length + 4 <= $totalDataCount * 8){ + if($this->bitBuffer->length + 4 <= $MAX_BITS){ $this->bitBuffer->put(0, 4); } @@ -552,70 +529,82 @@ class QRCode{ // padding while(true){ - if($this->bitBuffer->length >= $totalDataCount * 8){ + if($this->bitBuffer->length >= $MAX_BITS){ break; } - $this->bitBuffer->put(self::QR_PAD0, 8); + $this->bitBuffer->put(QRConst::PAD0, 8); - if($this->bitBuffer->length >= $totalDataCount * 8){ + if($this->bitBuffer->length >= $MAX_BITS){ break; } - $this->bitBuffer->put(self::QR_PAD1, 8); + $this->bitBuffer->put(QRConst::PAD1, 8); } - foreach($this->rsBlockList as $r => &$_rsData){ - $this->rsBlock->totalCount = $_rsData[0]; - $this->rsBlock->dataCount = $_rsData[1]; + } - $maxDcCount = max($maxDcCount, $this->rsBlock->dataCount); - $maxEcCount = max($maxEcCount, $this->rsBlock->totalCount - $this->rsBlock->dataCount); + /** + * @return array + * @throws \chillerlan\QRCode\QRCodeException + */ + protected function createBytes(){ + $totalCodeCount = $maxDcCount = $maxEcCount = $offset = $index = 0; + $rsBlocks = Util::getRSBlocks($this->typeNumber, $this->errorCorrectLevel); + $rsBlockCount = count($rsBlocks); + $dcdata = $ecdata = array_fill(0, $rsBlockCount, null); - $dcdata[$r] = array_fill(0, $this->rsBlock->dataCount, null); + foreach($rsBlocks as $key => $value){ + $rsBlockTotal = $value[0]; + $rsBlockDataCount = $value[1]; - $rsPoly = new Polynomial; - $modPoly = new Polynomial; + $maxDcCount = max($maxDcCount, $rsBlockDataCount); + $maxEcCount = max($maxEcCount, $rsBlockTotal - $rsBlockDataCount); - foreach($dcdata[$r] as $i => &$_dcdata){ + $dcdata[$key] = array_fill(0, $rsBlockDataCount, null); + + foreach($dcdata[$key] as $i => &$_dcdata){ $bdata = $this->bitBuffer->buffer; $_dcdata = 0xff & $bdata[$i + $offset]; } - $offset += $this->rsBlock->dataCount; + $offset += $rsBlockDataCount; - for($i = 0; $i < $this->rsBlock->totalCount - $this->rsBlock->dataCount; $i++){ + $rsPoly = new Polynomial; + $modPoly = new Polynomial; + + for($i = 0; $i < $rsBlockTotal - $rsBlockDataCount; $i++){ $modPoly->setNum([1, $modPoly->gexp($i)]); $rsPoly->multiply($modPoly->num); } $rsPolyCount = count($rsPoly->num); - $modPoly->setNum($dcdata[$r], $rsPolyCount - 1)->mod($rsPoly->num); - $ecdata[$r] = array_fill(0, $rsPolyCount - 1, null); - $add = count($modPoly->num) - count($ecdata[$r]); + $modPoly->setNum($dcdata[$key], $rsPolyCount - 1)->mod($rsPoly->num); + $ecdata[$key] = array_fill(0, $rsPolyCount - 1, null); + $add = count($modPoly->num) - count($ecdata[$key]); - foreach($ecdata[$r] as $i => &$_ecdata){ + foreach($ecdata[$key] as $i => &$_ecdata){ $modIndex = $i + $add; $_ecdata = $modIndex >= 0 ? $modPoly->num[$modIndex] : 0; } - $totalCodeCount += $this->rsBlock->totalCount; + $totalCodeCount += $rsBlockTotal; } $data = array_fill(0, $totalCodeCount, null); for($i = 0; $i < $maxDcCount; $i++){ - for($r = 0; $r < $rsBlockCount; $r++){ - if($i < count($dcdata[$r])){ - $data[$index++] = $dcdata[$r][$i]; + for($key = 0; $key < $rsBlockCount; $key++){ + if($i < count($dcdata[$key])){ + $data[$index++] = $dcdata[$key][$i]; } } } for($i = 0; $i < $maxEcCount; $i++){ - for($r = 0; $r < $rsBlockCount; $r++){ - if($i < count($ecdata[$r])){ - $data[$index++] = $ecdata[$r][$i]; + for($key = 0; $key < $rsBlockCount; $key++){ + if($i < count($ecdata[$key])){ + $data[$index++] = $ecdata[$key][$i]; } } } @@ -623,98 +612,4 @@ class QRCode{ return $data; } - /** - * added $fg (foreground), $bg (background), and $bgtrans (use transparent bg) parameters - * also added some simple error checking on parameters - * updated 2015.07.27 ~ DoktorJ - * - * @param int $size - * @param int $margin - * @param int $fg - * @param int $bg - * @param bool|false $bgtrans - * - * @return resource - */ - 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->moduleCount * $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->util->hex2rgb($fg); - $bgrgb = $this->util->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->moduleCount; $r++){ - for($c = 0; $c < $this->moduleCount; $c++){ - if($this->modules[$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; - } - - /** - * @return string - */ - public function printHTML(){ - $html = ''; - - for($row = 0; $row < $this->moduleCount; $row++){ - $html .= ''; - - for($col = 0; $col < $this->moduleCount; $col++){ - $html .= ''; - } - - $html .= ''; - } - - $html .= '
'; - - return $html; - } - } diff --git a/src/QRCodeException.php b/src/QRCodeException.php index c67aa8267..b59ec658b 100644 --- a/src/QRCodeException.php +++ b/src/QRCodeException.php @@ -4,13 +4,13 @@ * * @filesource QRCodeException.php * @created 27.11.2015 - * @package codemasher\QRCode + * @package chillerlan\QRCode * @author Smiley * @copyright 2015 Smiley * @license MIT */ -namespace codemasher\QRCode; +namespace chillerlan\QRCode; use Exception; diff --git a/src/QRConst.php b/src/QRConst.php index 3aefb28a8..c6ccfa321 100644 --- a/src/QRConst.php +++ b/src/QRConst.php @@ -1,25 +1,70 @@ * @copyright 2015 Smiley * @license MIT */ -namespace codemasher\QRCode; +namespace chillerlan\QRCode; /** - * Class QRConst + * const ALL THE THINGS! ~John Carmack */ class QRConst{ - const MODE_NUMBER = 1 << 0; + 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%. + + const RSBLOCK = [ + self::ERROR_CORRECT_LEVEL_L => 0, + self::ERROR_CORRECT_LEVEL_M => 1, + self::ERROR_CORRECT_LEVEL_Q => 2, + self::ERROR_CORRECT_LEVEL_H => 3, + ]; + + // max bits @ ec level L:07 M:15 Q:25 H:30 % + const TYPE_01 = 1; // 152 128 104 72 + const TYPE_02 = 2; // 272 224 176 128 + const TYPE_03 = 3; // 440 352 272 208 + const TYPE_04 = 4; // 640 512 384 288 + const TYPE_05 = 5; // 864 688 496 368 + const TYPE_06 = 6; // 1088 864 608 480 + const TYPE_07 = 7; // 1248 992 704 528 + const TYPE_08 = 8; // 1552 1232 880 688 + const TYPE_09 = 9; // 1856 1456 1056 800 + const TYPE_10 = 10; // 2192 1728 1232 976 + + const MAX_BITS = [ + self::TYPE_01 => [ 128, 152, 72, 104], + self::TYPE_02 => [ 224, 272, 128, 176], + self::TYPE_03 => [ 352, 440, 208, 272], + self::TYPE_04 => [ 512, 640, 288, 384], + self::TYPE_05 => [ 688, 864, 368, 496], + self::TYPE_06 => [ 864, 1088, 480, 608], + self::TYPE_07 => [ 992, 1248, 528, 704], + self::TYPE_08 => [1232, 1552, 688, 880], + self::TYPE_09 => [1456, 1856, 800, 1056], + self::TYPE_10 => [1728, 2192, 976, 1232], + ]; + + const MODE_NUMBER = 1 << 0; const MODE_ALPHANUM = 1 << 1; - const MODE_BYTE = 1 << 2; - const MODE_KANJI = 1 << 3; + const MODE_BYTE = 1 << 2; + const MODE_KANJI = 1 << 3; + + const MODE = [ + self::MODE_NUMBER => 0, + self::MODE_ALPHANUM => 1, + self::MODE_BYTE => 2, + self::MODE_KANJI => 3, + ]; const MASK_PATTERN000 = 0; const MASK_PATTERN001 = 1; @@ -30,9 +75,132 @@ class QRConst{ 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%. + const G15_MASK = (1 << 14)|(1 << 12)|(1 << 10)|(1 << 4)|(1 << 1); + const G15 = (1 << 10)|(1 << 8)|(1 << 5)|(1 << 4)|(1 << 2)|(1 << 1)|(1 << 0); + const G18 = (1 << 12)|(1 << 11)|(1 << 10)|(1 << 9)|(1 << 8)|(1 << 5)|(1 << 2)|(1 << 0); + + const PAD0 = 0xEC; + const PAD1 = 0x11; + + const CHAR_MAP = [ + 36 => ' ', + 37 => '$', + 38 => '%', + 39 => '*', + 40 => '+', + 41 => '-', + 42 => '.', + 43 => '/', + 44 => ':', + ]; + + const 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]], + ]; + + const BLOCK_TABLE = [ + // 1 + [1, 26, 19], // L + [1, 26, 16], // M + [1, 26, 13], // Q + [1, 26, 9], // H + // 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], + ]; + + const PATTERN_POSITION = [ + [], + [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], + ]; } diff --git a/src/RSBlock.php b/src/RSBlock.php deleted file mode 100644 index 797c98f3b..000000000 --- a/src/RSBlock.php +++ /dev/null @@ -1,144 +0,0 @@ - - * @copyright 2015 Smiley - * @license MIT - */ - -namespace codemasher\QRCode; - -use codemasher\QRCode\QRConst; -use codemasher\QRCode\QRCodeException; - -/** - * Class RSBlock - */ -class RSBlock{ - - /** - * @var int - */ - public $totalCount; - - /** - * @var int - */ - public $dataCount; - - /** - * @var array - */ - protected $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], - - ]; - - /** - * @var array - */ - protected $RSBLOCK = [ - QRConst::ERROR_CORRECT_LEVEL_L => 0, - QRConst::ERROR_CORRECT_LEVEL_M => 1, - QRConst::ERROR_CORRECT_LEVEL_Q => 2, - QRConst::ERROR_CORRECT_LEVEL_H => 3, - ]; - - /** - * @param $typeNumber - * @param $errorCorrectLevel - * - * @return array - * @throws \codemasher\QRCode\QRCodeException - */ - public function getRSBlocks($typeNumber, $errorCorrectLevel){ - - if(!isset($this->RSBLOCK[$errorCorrectLevel])){ - throw new QRCodeException('$typeNumber: '.$typeNumber.' / $errorCorrectLevel: '.$errorCorrectLevel.PHP_EOL.print_r($this->RSBLOCK, true)); - } - - $rsBlock = $this->BLOCK_TABLE[($typeNumber - 1) * 4 + $this->RSBLOCK[$errorCorrectLevel]]; - - $list = []; - $length = count($rsBlock) / 3; - 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[] = [$totalCount, $dataCount]; - } - } - - return $list; - } - -} diff --git a/src/Util.php b/src/Util.php index e919813dc..d47ccf0ed 100644 --- a/src/Util.php +++ b/src/Util.php @@ -1,138 +1,85 @@ * @copyright 2015 Smiley * @license MIT */ -namespace codemasher\QRCode; +namespace chillerlan\QRCode; /** - * 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 + * @param int $typeNumber + * @param int $errorCorrectLevel + * + * @return array + * @throws \chillerlan\QRCode\QRCodeException */ - public $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]], - ]; + public static function getRSBlocks($typeNumber, $errorCorrectLevel){ + // PHP5 compat + $RSBLOCK = QRConst::RSBLOCK; + $BLOCK_TABLE = QRConst::BLOCK_TABLE; - /** - * @var array - */ - public $PATTERN_POSITION = [ - [], - [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], - ]; + if(!isset($RSBLOCK[$errorCorrectLevel])){ + throw new QRCodeException('$typeNumber: '.$typeNumber.' / $errorCorrectLevel: '.$errorCorrectLevel.PHP_EOL.print_r($RSBLOCK, true)); + } - /** - * @var array - */ - protected $ERROR_CORRECT_LEVEL = [ - QRConst::ERROR_CORRECT_LEVEL_L => 0, - QRConst::ERROR_CORRECT_LEVEL_M => 1, - QRConst::ERROR_CORRECT_LEVEL_Q => 2, - QRConst::ERROR_CORRECT_LEVEL_H => 3, - ]; + $rsBlock = $BLOCK_TABLE[($typeNumber - 1) * 4 + $RSBLOCK[$errorCorrectLevel]]; - /** - * @var array - */ - protected $MODE = [ - QRConst::MODE_NUMBER => 0, - QRConst::MODE_ALPHANUM => 1, - QRConst::MODE_BYTE => 2, - QRConst::MODE_KANJI => 3, - ]; + $list = []; + $length = count($rsBlock) / 3; + for($i = 0; $i < $length; $i++){ + for($j = 0; $j < $rsBlock[$i * 3 + 0]; $j++){ + $list[] = [$rsBlock[$i * 3 + 1], $rsBlock[$i * 3 + 2]]; + } + } + + return $list; + } /** * @param int $typeNumber * @param int $mode * @param int $ecLevel * - * @return mixed - * @throws \codemasher\QRCode\QRCodeException + * @return int + * @throws \chillerlan\QRCode\QRCodeException */ - public function getMaxLength($typeNumber, $mode, $ecLevel){ + public static function getMaxLength($typeNumber, $mode, $ecLevel){ + $RSBLOCK = QRConst::RSBLOCK; + $MAX_LENGTH = QRConst::MAX_LENGTH; + $MODE = QRConst::MODE; - if(!isset($this->ERROR_CORRECT_LEVEL[$ecLevel])){ + if(!isset($RSBLOCK[$ecLevel])){ throw new QRCodeException('$_err: '.$ecLevel); } - if(!isset($this->MODE[$mode])){ + if(!isset($MODE[$mode])){ throw new QRCodeException('$_mode: '.$mode); } - return $this->MAX_LENGTH[$typeNumber - 1][$this->ERROR_CORRECT_LEVEL[$ecLevel]][$this->MODE[$mode]]; + return $MAX_LENGTH[$typeNumber - 1][$RSBLOCK[$ecLevel]][$MODE[$mode]]; } /** - * @param $s + * @param string $s * * @return int */ - public function getMode($s){ + public static function getMode($s){ switch(true){ - case $this->isAlphaNum($s): return $this->isNumber($s) ? QRConst::MODE_NUMBER : QRConst::MODE_ALPHANUM; - case $this->isKanji($s) : return QRConst::MODE_KANJI; + case self::isAlphaNum($s): return self::isNumber($s) ? QRConst::MODE_NUMBER : QRConst::MODE_ALPHANUM; + case self::isKanji($s) : return QRConst::MODE_KANJI; default: return QRConst::MODE_BYTE; } @@ -140,11 +87,11 @@ class Util{ } /** - * @param $s + * @param string $s * * @return bool */ - protected function isNumber($s){ + protected static function isNumber($s){ $len = strlen($s); for($i = 0; $i < $len; $i++){ @@ -159,11 +106,11 @@ class Util{ } /** - * @param $s + * @param string $s * * @return bool */ - protected function isAlphaNum($s){ + protected static function isAlphaNum($s){ $len = strlen($s); for($i = 0; $i < $len; $i++){ @@ -178,11 +125,11 @@ class Util{ } /** - * @param $s + * @param string $s * * @return bool */ - protected function isKanji($s){ + protected static function isKanji($s){ $i = 0; $len = strlen($s); @@ -204,41 +151,41 @@ class Util{ } /** - * @param $data + * @param int $data * * @return int */ - public function getBCHTypeInfo($data){ + public static 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))); + while(self::getBCHDigit($d) - self::getBCHDigit(QRConst::G15) >= 0){ + $d ^= (QRConst::G15 << (self::getBCHDigit($d) - self::getBCHDigit(QRConst::G15))); } - return (($data << 10)|$d)^self::QR_G15_MASK; + return (($data << 10)|$d)^QRConst::G15_MASK; } /** - * @param $data + * @param int $data * * @return int */ - public function getBCHTypeNumber($data){ + public static 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))); + while(self::getBCHDigit($d) - self::getBCHDigit(QRConst::G18) >= 0){ + $d ^= (QRConst::G18 << (self::getBCHDigit($d) - self::getBCHDigit(QRConst::G18))); } return ($data << 12)|$d; } /** - * @param $data + * @param int $data * * @return int */ - public function getBCHDigit($data){ + public static function getBCHDigit($data){ $digit = 0; while($data !== 0){ @@ -249,7 +196,6 @@ class Util{ return $digit; } - /** * used for converting fg/bg colors (e.g. #0000ff = 0x0000FF) - added 2015.07.27 ~ DoktorJ * @@ -257,12 +203,62 @@ class Util{ * * @return array */ - public function hex2rgb($hex = 0x0){ + public static function hex2rgb($hex = 0x0){ return [ - 'r' => floor($hex / 65536), - 'g' => floor($hex / 256) % 256, - 'b' => $hex % 256, + 'r' => (int)floor($hex / 65536), + 'g' => (int)floor($hex / 256) % 256, + 'b' => (int)$hex % 256, ]; } + /** + * @param string $c + * + * @return int + * @throws \chillerlan\QRCode\QRCodeException + */ + public static function getCharCode($c){ + $c = ord($c); + + switch(true){ + case ord('0') <= $c && $c <= ord('9'): return $c - ord('0'); + case ord('A') <= $c && $c <= ord('Z'): return $c - ord('A') + 10; + default: + foreach(QRConst::CHAR_MAP as $i => $char){ + if(ord($char) === $c){ + return $i; + } + } + } + + throw new QRCodeException('illegal char: '.$c); + } + + /** + * @param string $string + * + * @return int + * @throws \chillerlan\QRCode\QRCodeException + */ + public static function parseInt($string){ + $num = 0; + + $len = strlen($string); + for($i = 0; $i < $len; $i++){ + $c = ord($string[$i]); + $ord0 = ord('0'); + + if($ord0 <= $c && $c <= ord('9')){ + $c = $c - $ord0; + } + else{ + throw new QRCodeException('illegal char: '.$c); + } + + $num = $num * 10 + $c; + } + + return $num; + } + }