diff --git a/src/Data/AlphaNum.php b/src/Data/AlphaNum.php index 1c9a2ce69..0ec61621f 100644 --- a/src/Data/AlphaNum.php +++ b/src/Data/AlphaNum.php @@ -13,14 +13,26 @@ namespace chillerlan\QRCode\Data; use chillerlan\QRCode\BitBuffer; +use chillerlan\QRCode\QRCodeException; use chillerlan\QRCode\QRConst; -use chillerlan\QRCode\Util; /** * */ class AlphaNum extends QRDataBase implements QRDataInterface{ + const CHAR_MAP = [ + 36 => ' ', + 37 => '$', + 38 => '%', + 39 => '*', + 40 => '+', + 41 => '-', + 42 => '.', + 43 => '/', + 44 => ':', + ]; + /** * @var int */ @@ -38,14 +50,37 @@ class AlphaNum extends QRDataBase implements QRDataInterface{ $i = 0; while($i + 1 < $this->dataLength){ - $buffer->put(Util::getCharCode($this->data[$i]) * 45 + Util::getCharCode($this->data[$i + 1]), 11); + $buffer->put($this->getCharCode($this->data[$i]) * 45 + $this->getCharCode($this->data[$i + 1]), 11); $i += 2; } if($i < $this->dataLength){ - $buffer->put(Util::getCharCode($this->data[$i]), 6); + $buffer->put($this->getCharCode($this->data[$i]), 6); } } + /** + * @param string $c + * + * @return int + * @throws \chillerlan\QRCode\QRCodeException + */ + private 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(self::CHAR_MAP as $i => $char){ + if(ord($char) === $c){ + return $i; + } + } + } + + throw new QRCodeException('illegal char: '.$c); + } + } diff --git a/src/Data/Number.php b/src/Data/Number.php index 5693d41b0..01fce7924 100644 --- a/src/Data/Number.php +++ b/src/Data/Number.php @@ -13,8 +13,8 @@ namespace chillerlan\QRCode\Data; use chillerlan\QRCode\BitBuffer; +use chillerlan\QRCode\QRCodeException; use chillerlan\QRCode\QRConst; -use chillerlan\QRCode\Util; /** * @@ -38,21 +38,48 @@ class Number extends QRDataBase implements QRDataInterface{ $i = 0; while($i + 2 < $this->dataLength){ - $buffer->put(Util::parseInt(substr($this->data, $i, 3)), 10); + $buffer->put($this->parseInt(substr($this->data, $i, 3)), 10); $i += 3; } if($i < $this->dataLength){ if($this->dataLength - $i === 1){ - $buffer->put(Util::parseInt(substr($this->data, $i, $i + 1)), 4); + $buffer->put($this->parseInt(substr($this->data, $i, $i + 1)), 4); } else if($this->dataLength - $i === 2){ - $buffer->put(Util::parseInt(substr($this->data, $i, $i + 2)), 7); + $buffer->put($this->parseInt(substr($this->data, $i, $i + 2)), 7); } } } + /** + * @param string $string + * + * @return int + * @throws \chillerlan\QRCode\QRCodeException + */ + private 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; + } + } diff --git a/src/QRCode.php b/src/QRCode.php index c56d436c4..299283761 100644 --- a/src/QRCode.php +++ b/src/QRCode.php @@ -16,6 +16,7 @@ use chillerlan\QRCode\Output\QROutputInterface; /** * @link https://github.com/kazuhikoarase/qrcode-generator/tree/master/php + * @link http://www.thonky.com/qr-code-tutorial/ */ class QRCode{ @@ -117,7 +118,18 @@ class QRCode{ throw new QRCodeException('Invalid error correct level: '.$options->errorCorrectLevel); } - $mode = Util::getMode($data); + switch(true){ + case Util::isAlphaNum($data): + $mode = Util::isNumber($data) ? QRConst::MODE_NUMBER : QRConst::MODE_ALPHANUM; + break; + case Util::isKanji($data) : + $mode = QRConst::MODE_KANJI; + break; + default: + $mode = QRConst::MODE_BYTE; + break; + } + $qrDataInterface = __NAMESPACE__.'\\Data\\'.[ QRConst::MODE_ALPHANUM => 'AlphaNum', @@ -137,7 +149,7 @@ class QRCode{ : $this->qrDataInterface->dataLength; for($type = 1; $type <= 10; $type++){ - if($length <= Util::getMaxLength($type, $mode, $this->errorCorrectLevel)){ + if($length <= $this->getMaxLength($type, $mode, $this->errorCorrectLevel)){ $this->typeNumber = $type; return $this; @@ -281,13 +293,83 @@ class QRCode{ return $this->matrix; } + /** + * @param int $typeNumber + * @param int $mode + * @param int $ecLevel + * + * @return int + * @throws \chillerlan\QRCode\QRCodeException + */ + protected static function getMaxLength($typeNumber, $mode, $ecLevel){ + $RSBLOCK = QRConst::RSBLOCK; + $MAX_LENGTH = QRConst::MAX_LENGTH; + $MODE = QRConst::MODE; + + if(!isset($RSBLOCK[$ecLevel])){ + throw new QRCodeException('$_err: '.$ecLevel); + } + + if(!isset($MODE[$mode])){ + throw new QRCodeException('$_mode: '.$mode); + } + + return $MAX_LENGTH[$typeNumber - 1][$RSBLOCK[$ecLevel]][$MODE[$mode]]; + } + + /** + * @param int $data + * + * @return int + */ + protected static function getBCHTypeInfo($data){ + $d = $data << 10; + + while(self::getBCHDigit($d) - self::getBCHDigit(QRConst::G15) >= 0){ + $d ^= (QRConst::G15 << (self::getBCHDigit($d) - self::getBCHDigit(QRConst::G15))); + } + + return (($data << 10)|$d)^QRConst::G15_MASK; + } + + /** + * @param int $data + * + * @return int + */ + protected static function getBCHTypeNumber($data){ + $d = $data << 12; + + while(self::getBCHDigit($d) - self::getBCHDigit(QRConst::G18) >= 0){ + $d ^= (QRConst::G18 << (self::getBCHDigit($d) - self::getBCHDigit(QRConst::G18))); + } + + return ($data << 12)|$d; + } + + /** + * @param int $data + * + * @return int + */ + protected static function getBCHDigit($data){ + $digit = 0; + + while($data !== 0){ + $digit++; + $data >>= 1; + } + + return $digit; + } + /** * @param bool $test * @param int $pattern */ protected function setTypeInfo($test, $pattern){ $this->setPattern(); - $bits = Util::getBCHTypeInfo(($this->errorCorrectLevel << 3) | $pattern); + $bits = self::getBCHTypeInfo(($this->errorCorrectLevel << 3) | $pattern); for($i = 0; $i < 15; $i++){ $mod = !$test && (($bits >> $i) & 1) === 1; @@ -376,7 +458,7 @@ class QRCode{ * @return $this */ protected function setTypeNumber($test){ - $bits = Util::getBCHTypeNumber($this->typeNumber); + $bits = self::getBCHTypeNumber($this->typeNumber); for($i = 0; $i < 18; $i++){ $a = (int)floor($i / 3); @@ -540,13 +622,42 @@ class QRCode{ } + /** + * @param int $typeNumber + * @param int $errorCorrectLevel + * + * @return array + * @throws \chillerlan\QRCode\QRCodeException + */ + protected static function getRSBlocks($typeNumber, $errorCorrectLevel){ + // PHP5 compat + $RSBLOCK = QRConst::RSBLOCK; + $BLOCK_TABLE = QRConst::BLOCK_TABLE; + + if(!isset($RSBLOCK[$errorCorrectLevel])){ + throw new QRCodeException('$typeNumber: '.$typeNumber.' / $errorCorrectLevel: '.$errorCorrectLevel.PHP_EOL.print_r($RSBLOCK, true)); + } + + $rsBlock = $BLOCK_TABLE[($typeNumber - 1) * 4 + $RSBLOCK[$errorCorrectLevel]]; + + $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; + } + /** * @return array * @throws \chillerlan\QRCode\QRCodeException */ protected function createBytes(){ $totalCodeCount = $maxDcCount = $maxEcCount = $offset = $index = 0; - $rsBlocks = Util::getRSBlocks($this->typeNumber, $this->errorCorrectLevel); + $rsBlocks = $this->getRSBlocks($this->typeNumber, $this->errorCorrectLevel); $rsBlockCount = count($rsBlocks); $dcdata = $ecdata = array_fill(0, $rsBlockCount, null); diff --git a/src/QRConst.php b/src/QRConst.php index 2989661cd..77876f305 100644 --- a/src/QRConst.php +++ b/src/QRConst.php @@ -65,18 +65,6 @@ class QRConst{ 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]], diff --git a/src/Util.php b/src/Util.php index 09fa5c6e6..014c6ff22 100644 --- a/src/Util.php +++ b/src/Util.php @@ -17,75 +17,6 @@ namespace chillerlan\QRCode; */ class Util{ - /** - * @param int $typeNumber - * @param int $errorCorrectLevel - * - * @return array - * @throws \chillerlan\QRCode\QRCodeException - */ - public static function getRSBlocks($typeNumber, $errorCorrectLevel){ - // PHP5 compat - $RSBLOCK = QRConst::RSBLOCK; - $BLOCK_TABLE = QRConst::BLOCK_TABLE; - - if(!isset($RSBLOCK[$errorCorrectLevel])){ - throw new QRCodeException('$typeNumber: '.$typeNumber.' / $errorCorrectLevel: '.$errorCorrectLevel.PHP_EOL.print_r($RSBLOCK, true)); - } - - $rsBlock = $BLOCK_TABLE[($typeNumber - 1) * 4 + $RSBLOCK[$errorCorrectLevel]]; - - $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 int - * @throws \chillerlan\QRCode\QRCodeException - */ - public static function getMaxLength($typeNumber, $mode, $ecLevel){ - $RSBLOCK = QRConst::RSBLOCK; - $MAX_LENGTH = QRConst::MAX_LENGTH; - $MODE = QRConst::MODE; - - if(!isset($RSBLOCK[$ecLevel])){ - throw new QRCodeException('$_err: '.$ecLevel); - } - - if(!isset($MODE[$mode])){ - throw new QRCodeException('$_mode: '.$mode); - } - - return $MAX_LENGTH[$typeNumber - 1][$RSBLOCK[$ecLevel]][$MODE[$mode]]; - } - - /** - * @param string $s - * - * @return int - */ - public static function getMode($s){ - - switch(true){ - 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; - } - - } - /** * @param string $s * @@ -150,100 +81,4 @@ class Util{ return true; } - /** - * @param int $data - * - * @return int - */ - public static function getBCHTypeInfo($data){ - $d = $data << 10; - - while(self::getBCHDigit($d) - self::getBCHDigit(QRConst::G15) >= 0){ - $d ^= (QRConst::G15 << (self::getBCHDigit($d) - self::getBCHDigit(QRConst::G15))); - } - - return (($data << 10)|$d)^QRConst::G15_MASK; - } - - /** - * @param int $data - * - * @return int - */ - public static function getBCHTypeNumber($data){ - $d = $data << 12; - - while(self::getBCHDigit($d) - self::getBCHDigit(QRConst::G18) >= 0){ - $d ^= (QRConst::G18 << (self::getBCHDigit($d) - self::getBCHDigit(QRConst::G18))); - } - - return ($data << 12)|$d; - } - - /** - * @param int $data - * - * @return int - */ - public static function getBCHDigit($data){ - $digit = 0; - - while($data !== 0){ - $digit++; - $data >>= 1; - } - - return $digit; - } - - /** - * @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; - } - }