diff --git a/src/Data/AlphaNum.php b/src/Data/AlphaNum.php index 71ace3291..f3ff54dab 100644 --- a/src/Data/AlphaNum.php +++ b/src/Data/AlphaNum.php @@ -14,7 +14,7 @@ namespace chillerlan\QRCode\Data; use chillerlan\QRCode\Common\{BitBuffer, Mode}; -use function ceil, ord, sprintf, str_split; +use function array_flip, ceil, ord, sprintf, str_split; /** * Alphanumeric mode: 0 to 9, A to Z, space, $ % * + - . / : @@ -98,4 +98,49 @@ final class AlphaNum extends QRDataModeAbstract{ return self::CHAR_MAP_ALPHANUM[$chr]; } + /** + * @inheritdoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read(Mode::getLengthBitsForVersion(self::$datamode, $versionNumber)); + $charmap = array_flip(self::CHAR_MAP_ALPHANUM); + + // @todo + $toAlphaNumericChar = function(int $ord) use ($charmap):string{ + + if(isset($charmap[$ord])){ + return $charmap[$ord]; + } + + throw new QRCodeDataException('invalid character value: '.$ord); + }; + + $result = ''; + // Read two characters at a time + while($length > 1){ + + if($bitBuffer->available() < 11){ + throw new QRCodeDataException('not enough bits available'); + } + + $nextTwoCharsBits = $bitBuffer->read(11); + $result .= $toAlphaNumericChar($nextTwoCharsBits / 45); + $result .= $toAlphaNumericChar($nextTwoCharsBits % 45); + $length -= 2; + } + + if($length === 1){ + // special case: one character left + if($bitBuffer->available() < 6){ + throw new QRCodeDataException('not enough bits available'); + } + + $result .= $toAlphaNumericChar($bitBuffer->read(6)); + } + + return $result; + } + } diff --git a/src/Data/Byte.php b/src/Data/Byte.php index 3043b1713..229952d0a 100644 --- a/src/Data/Byte.php +++ b/src/Data/Byte.php @@ -60,4 +60,25 @@ final class Byte extends QRDataModeAbstract{ } + /** + * @inheritdoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read(Mode::getLengthBitsForVersion(self::$datamode, $versionNumber)); + + if($bitBuffer->available() < 8 * $length){ + throw new QRCodeDataException('not enough bits available'); + } + + $readBytes = ''; + + for($i = 0; $i < $length; $i++){ + $readBytes .= \chr($bitBuffer->read(8)); + } + + return $readBytes; + } + } diff --git a/src/Data/ECI.php b/src/Data/ECI.php index 9271aba38..a3e08130b 100644 --- a/src/Data/ECI.php +++ b/src/Data/ECI.php @@ -43,17 +43,7 @@ final class ECI extends QRDataModeAbstract{ return 8; } - /** - * Unused, but required as per interface - * - * @inheritDoc - * @codeCoverageIgnore - */ - public static function validateString(string $string):bool{ - return true; - } - - /** + /** * @inheritDoc */ public function write(BitBuffer $bitBuffer, int $versionNumber):void{ @@ -63,4 +53,46 @@ final class ECI extends QRDataModeAbstract{ ; } + /** + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function parseValue(BitBuffer $bitBuffer):int{ + $firstByte = $bitBuffer->read(8); + + if(($firstByte & 0x80) === 0){ + // just one byte + return $firstByte & 0x7f; + } + + if(($firstByte & 0xc0) === 0x80){ + // two bytes + $secondByte = $bitBuffer->read(8); + + return (($firstByte & 0x3f) << 8) | $secondByte; + } + + if(($firstByte & 0xe0) === 0xC0){ + // three bytes + $secondThirdBytes = $bitBuffer->read(16); + + return (($firstByte & 0x1f) << 16) | $secondThirdBytes; + } + + throw new QRCodeDataException('error decoding ECI value'); + } + + /** + * @codeCoverageIgnore Unused, but required as per interface + */ + public static function validateString(string $string):bool{ + return true; + } + + /** + * @codeCoverageIgnore Unused, but required as per interface + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + return ''; + } + } diff --git a/src/Data/Kanji.php b/src/Data/Kanji.php index 191d98535..856b54779 100644 --- a/src/Data/Kanji.php +++ b/src/Data/Kanji.php @@ -14,7 +14,7 @@ namespace chillerlan\QRCode\Data; use chillerlan\QRCode\Common\{BitBuffer, Mode}; -use function mb_convert_encoding, mb_detect_encoding, mb_strlen, ord, sprintf, strlen; +use function chr, implode, mb_convert_encoding, mb_detect_encoding, mb_internal_encoding, mb_strlen, ord, sprintf, strlen; /** * Kanji mode: double-byte characters from the Shift JIS character set @@ -57,7 +57,7 @@ final class Kanji extends QRDataModeAbstract{ while($i + 1 < $len){ $c = ((0xff & ord($string[$i])) << 8) | (0xff & ord($string[$i + 1])); - if(!($c >= 0x8140 && $c <= 0x9FFC) && !($c >= 0xE040 && $c <= 0xEBBF)){ + if(!($c >= 0x8140 && $c <= 0x9ffc) && !($c >= 0xe040 && $c <= 0xebbf)){ return false; } @@ -84,17 +84,17 @@ final class Kanji extends QRDataModeAbstract{ for($i = 0; $i + 1 < $len; $i += 2){ $c = ((0xff & ord($this->data[$i])) << 8) | (0xff & ord($this->data[$i + 1])); - if($c >= 0x8140 && $c <= 0x9FFC){ + if($c >= 0x8140 && $c <= 0x9ffC){ $c -= 0x8140; } - elseif($c >= 0xE040 && $c <= 0xEBBF){ - $c -= 0xC140; + elseif($c >= 0xe040 && $c <= 0xebbf){ + $c -= 0xc140; } else{ throw new QRCodeDataException(sprintf('illegal char at %d [%d]', $i + 1, $c)); } - $bitBuffer->put(((($c >> 8) & 0xff) * 0xC0) + ($c & 0xff), 13); + $bitBuffer->put(((($c >> 8) & 0xff) * 0xc0) + ($c & 0xff), 13); } if($i < $len){ @@ -103,4 +103,37 @@ final class Kanji extends QRDataModeAbstract{ } + /** + * @inheritdoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read(Mode::getLengthBitsForVersion(self::$datamode, $versionNumber)); + + if($bitBuffer->available() < $length * 13){ + throw new QRCodeDataException('not enough bits available'); + } + + $buffer = []; + $offset = 0; + + while($length > 0){ + // Each 13 bits encodes a 2-byte character + $twoBytes = $bitBuffer->read(13); + $assembledTwoBytes = (($twoBytes / 0x0c0) << 8) | ($twoBytes % 0x0c0); + + $assembledTwoBytes += ($assembledTwoBytes < 0x01f00) + ? 0x08140 // In the 0x8140 to 0x9FFC range + : 0x0c140; // In the 0xE040 to 0xEBBF range + + $buffer[$offset] = chr(0xff & ($assembledTwoBytes >> 8)); + $buffer[$offset + 1] = chr(0xff & $assembledTwoBytes); + $offset += 2; + $length--; + } + + return mb_convert_encoding(implode($buffer), mb_internal_encoding(), 'SJIS'); + } + } diff --git a/src/Data/Number.php b/src/Data/Number.php index 7260e4bcf..01ae36ecf 100644 --- a/src/Data/Number.php +++ b/src/Data/Number.php @@ -14,7 +14,7 @@ namespace chillerlan\QRCode\Data; use chillerlan\QRCode\Common\{BitBuffer, Mode}; -use function ceil, ord, sprintf, str_split, substr; +use function array_flip, ceil, ord, sprintf, str_split, substr; /** * Numeric mode: decimal digits 0 to 9 @@ -110,4 +110,77 @@ final class Number extends QRDataModeAbstract{ return $num; } + /** + * @inheritdoc + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read(Mode::getLengthBitsForVersion(self::$datamode, $versionNumber)); + $charmap = array_flip(self::CHAR_MAP_NUMBER); + + // @todo + $toNumericChar = function(int $ord) use ($charmap):string{ + + if(isset($charmap[$ord])){ + return $charmap[$ord]; + } + + throw new QRCodeDataException('invalid character value: '.$ord); + }; + + $result = ''; + // Read three digits at a time + while($length >= 3){ + // Each 10 bits encodes three digits + if($bitBuffer->available() < 10){ + throw new QRCodeDataException('not enough bits available'); + } + + $threeDigitsBits = $bitBuffer->read(10); + + if($threeDigitsBits >= 1000){ + throw new QRCodeDataException('error decoding numeric value'); + } + + $result .= $toNumericChar($threeDigitsBits / 100); + $result .= $toNumericChar(($threeDigitsBits / 10) % 10); + $result .= $toNumericChar($threeDigitsBits % 10); + + $length -= 3; + } + + if($length === 2){ + // Two digits left over to read, encoded in 7 bits + if($bitBuffer->available() < 7){ + throw new QRCodeDataException('not enough bits available'); + } + + $twoDigitsBits = $bitBuffer->read(7); + + if($twoDigitsBits >= 100){ + throw new QRCodeDataException('error decoding numeric value'); + } + + $result .= $toNumericChar($twoDigitsBits / 10); + $result .= $toNumericChar($twoDigitsBits % 10); + } + elseif($length === 1){ + // One digit left over to read + if($bitBuffer->available() < 4){ + throw new QRCodeDataException('not enough bits available'); + } + + $digitBits = $bitBuffer->read(4); + + if($digitBits >= 10){ + throw new QRCodeDataException('error decoding numeric value'); + } + + $result .= $toNumericChar($digitBits); + } + + return $result; + } + } diff --git a/src/Data/QRDataModeInterface.php b/src/Data/QRDataModeInterface.php index cb791c7ef..b69c127e6 100644 --- a/src/Data/QRDataModeInterface.php +++ b/src/Data/QRDataModeInterface.php @@ -42,4 +42,9 @@ interface QRDataModeInterface{ */ public function write(BitBuffer $bitBuffer, int $versionNumber):void; + /** + * reads a segment from the BitBuffer and decodes in the current data mode + */ + public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string; + }