|
|
<?php |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace Zxing\Qrcode\Decoder; |
|
|
|
|
|
use Zxing\DecodeHintType; |
|
|
use Zxing\FormatException; |
|
|
use Zxing\Common\BitSource; |
|
|
use Zxing\Common\CharacterSetECI; |
|
|
use Zxing\Common\DecoderResult; |
|
|
use Zxing\Common\StringUtils; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final class DecodedBitStreamParser |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static $ALPHANUMERIC_CHARS = [ |
|
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', |
|
|
'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', |
|
|
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', |
|
|
' ', '$', '%', '*', '+', '-', '.', '/', ':', |
|
|
]; |
|
|
private static $GB2312_SUBSET = 1; |
|
|
|
|
|
public static function decode($bytes, |
|
|
$version, |
|
|
$ecLevel, |
|
|
$hints) |
|
|
{ |
|
|
$bits = new BitSource($bytes); |
|
|
$result = ''; |
|
|
$byteSegments = []; |
|
|
$symbolSequence = -1; |
|
|
$parityData = -1; |
|
|
|
|
|
try { |
|
|
$currentCharacterSetECI = null; |
|
|
$fc1InEffect = false; |
|
|
$mode = ''; |
|
|
do { |
|
|
|
|
|
if ($bits->available() < 4) { |
|
|
|
|
|
$mode = Mode::$TERMINATOR; |
|
|
} else { |
|
|
$mode = Mode::forBits($bits->readBits(4)); |
|
|
} |
|
|
if ($mode != Mode::$TERMINATOR) { |
|
|
if ($mode == Mode::$FNC1_FIRST_POSITION || $mode == Mode::$FNC1_SECOND_POSITION) { |
|
|
|
|
|
$fc1InEffect = true; |
|
|
} else if ($mode == Mode::$STRUCTURED_APPEND) { |
|
|
if ($bits->available() < 16) { |
|
|
throw FormatException::getFormatInstance(); |
|
|
} |
|
|
|
|
|
|
|
|
$symbolSequence = $bits->readBits(8); |
|
|
$parityData = $bits->readBits(8); |
|
|
} else if ($mode == Mode::$ECI) { |
|
|
|
|
|
$value = self::parseECIValue($bits); |
|
|
$currentCharacterSetECI = CharacterSetECI::getCharacterSetECIByValue($value); |
|
|
if ($currentCharacterSetECI == null) { |
|
|
throw FormatException::getFormatInstance(); |
|
|
} |
|
|
} else { |
|
|
|
|
|
if ($mode == Mode::$HANZI) { |
|
|
|
|
|
$subset = $bits->readBits(4); |
|
|
$countHanzi = $bits->readBits($mode->getCharacterCountBits($version)); |
|
|
if ($subset == self::$GB2312_SUBSET) { |
|
|
self::decodeHanziSegment($bits, $result, $countHanzi); |
|
|
} |
|
|
} else { |
|
|
|
|
|
|
|
|
$count = $bits->readBits($mode->getCharacterCountBits($version)); |
|
|
if ($mode == Mode::$NUMERIC) { |
|
|
self::decodeNumericSegment($bits, $result, $count); |
|
|
} else if ($mode == Mode::$ALPHANUMERIC) { |
|
|
self::decodeAlphanumericSegment($bits, $result, $count, $fc1InEffect); |
|
|
} else if ($mode == Mode::$BYTE) { |
|
|
self::decodeByteSegment($bits, $result, $count, $currentCharacterSetECI, $byteSegments, $hints); |
|
|
} else if ($mode == Mode::$KANJI) { |
|
|
self::decodeKanjiSegment($bits, $result, $count); |
|
|
} else { |
|
|
throw FormatException::getFormatInstance(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} while ($mode != Mode::$TERMINATOR); |
|
|
} catch (\InvalidArgumentException $iae) { |
|
|
|
|
|
throw FormatException::getFormatInstance(); |
|
|
} |
|
|
|
|
|
return new DecoderResult($bytes, |
|
|
$result, |
|
|
empty($byteSegments) ? null : $byteSegments, |
|
|
$ecLevel == null ? null : 'L', |
|
|
$symbolSequence, |
|
|
$parityData); |
|
|
} |
|
|
|
|
|
private static function parseECIValue($bits) |
|
|
{ |
|
|
$firstByte = $bits->readBits(8); |
|
|
if (($firstByte & 0x80) == 0) { |
|
|
|
|
|
return $firstByte & 0x7F; |
|
|
} |
|
|
if (($firstByte & 0xC0) == 0x80) { |
|
|
|
|
|
$secondByte = $bits->readBits(8); |
|
|
|
|
|
return (($firstByte & 0x3F) << 8) | $secondByte; |
|
|
} |
|
|
if (($firstByte & 0xE0) == 0xC0) { |
|
|
|
|
|
$secondThirdBytes = $bits->readBits(16); |
|
|
|
|
|
return (($firstByte & 0x1F) << 16) | $secondThirdBytes; |
|
|
} |
|
|
throw FormatException::getFormatInstance(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static function decodeHanziSegment($bits, |
|
|
&$result, |
|
|
$count) |
|
|
{ |
|
|
|
|
|
if ($count * 13 > $bits->available()) { |
|
|
throw FormatException::getFormatInstance(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$buffer = fill_array(0, 2 * $count, 0); |
|
|
$offset = 0; |
|
|
while ($count > 0) { |
|
|
|
|
|
$twoBytes = $bits->readBits(13); |
|
|
$assembledTwoBytes = (($twoBytes / 0x060) << 8) | ($twoBytes % 0x060); |
|
|
if ($assembledTwoBytes < 0x003BF) { |
|
|
|
|
|
$assembledTwoBytes += 0x0A1A1; |
|
|
} else { |
|
|
|
|
|
$assembledTwoBytes += 0x0A6A1; |
|
|
} |
|
|
$buffer[$offset] = (($assembledTwoBytes >> 8) & 0xFF); |
|
|
$buffer[$offset + 1] = ($assembledTwoBytes & 0xFF); |
|
|
$offset += 2; |
|
|
$count--; |
|
|
} |
|
|
$result .= iconv('GB2312', 'UTF-8', implode($buffer)); |
|
|
} |
|
|
|
|
|
private static function decodeNumericSegment($bits, |
|
|
&$result, |
|
|
$count) |
|
|
{ |
|
|
|
|
|
while ($count >= 3) { |
|
|
|
|
|
if ($bits->available() < 10) { |
|
|
throw FormatException::getFormatInstance(); |
|
|
} |
|
|
$threeDigitsBits = $bits->readBits(10); |
|
|
if ($threeDigitsBits >= 1000) { |
|
|
throw FormatException::getFormatInstance(); |
|
|
} |
|
|
$result .= (self::toAlphaNumericChar($threeDigitsBits / 100)); |
|
|
$result .= (self::toAlphaNumericChar(($threeDigitsBits / 10) % 10)); |
|
|
$result .= (self::toAlphaNumericChar($threeDigitsBits % 10)); |
|
|
$count -= 3; |
|
|
} |
|
|
if ($count == 2) { |
|
|
|
|
|
if ($bits->available() < 7) { |
|
|
throw FormatException::getFormatInstance(); |
|
|
} |
|
|
$twoDigitsBits = $bits->readBits(7); |
|
|
if ($twoDigitsBits >= 100) { |
|
|
throw FormatException::getFormatInstance(); |
|
|
} |
|
|
$result .= (self::toAlphaNumericChar($twoDigitsBits / 10)); |
|
|
$result .= (self::toAlphaNumericChar($twoDigitsBits % 10)); |
|
|
} else if ($count == 1) { |
|
|
|
|
|
if ($bits->available() < 4) { |
|
|
throw FormatException::getFormatInstance(); |
|
|
} |
|
|
$digitBits = $bits->readBits(4); |
|
|
if ($digitBits >= 10) { |
|
|
throw FormatException::getFormatInstance(); |
|
|
} |
|
|
$result .= (self::toAlphaNumericChar($digitBits)); |
|
|
} |
|
|
} |
|
|
|
|
|
private static function toAlphaNumericChar($value) |
|
|
{ |
|
|
if ($value >= count(self::$ALPHANUMERIC_CHARS)) { |
|
|
throw FormatException::getFormatInstance(); |
|
|
} |
|
|
|
|
|
return self::$ALPHANUMERIC_CHARS[$value]; |
|
|
} |
|
|
|
|
|
private static function decodeAlphanumericSegment($bits, |
|
|
&$result, |
|
|
$count, |
|
|
$fc1InEffect) |
|
|
{ |
|
|
|
|
|
$start = strlen($result); |
|
|
while ($count > 1) { |
|
|
if ($bits->available() < 11) { |
|
|
throw FormatException::getFormatInstance(); |
|
|
} |
|
|
$nextTwoCharsBits = $bits->readBits(11); |
|
|
$result .= (self::toAlphaNumericChar($nextTwoCharsBits / 45)); |
|
|
$result .= (self::toAlphaNumericChar($nextTwoCharsBits % 45)); |
|
|
$count -= 2; |
|
|
} |
|
|
if ($count == 1) { |
|
|
|
|
|
if ($bits->available() < 6) { |
|
|
throw FormatException::getFormatInstance(); |
|
|
} |
|
|
$result .= self::toAlphaNumericChar($bits->readBits(6)); |
|
|
} |
|
|
|
|
|
if ($fc1InEffect) { |
|
|
|
|
|
for ($i = $start; $i < strlen($result); $i++) { |
|
|
if ($result[$i] == '%') { |
|
|
if ($i < strlen($result) - 1 && $result[$i + 1] == '%') { |
|
|
|
|
|
$result = substr_replace($result, '', $i + 1, 1); |
|
|
} else { |
|
|
|
|
|
$result . setCharAt($i, chr(0x1D)); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
private static function decodeByteSegment($bits, |
|
|
&$result, |
|
|
$count, |
|
|
$currentCharacterSetECI, |
|
|
&$byteSegments, |
|
|
$hints) |
|
|
{ |
|
|
|
|
|
if (8 * $count > $bits->available()) { |
|
|
throw FormatException::getFormatInstance(); |
|
|
} |
|
|
|
|
|
$readBytes = fill_array(0, $count, 0); |
|
|
for ($i = 0; $i < $count; $i++) { |
|
|
$readBytes[$i] = $bits->readBits(8); |
|
|
} |
|
|
$text = implode(array_map('chr', $readBytes)); |
|
|
$encoding = ''; |
|
|
if ($currentCharacterSetECI == null) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$encoding = mb_detect_encoding($text, $hints); |
|
|
} else { |
|
|
$encoding = $currentCharacterSetECI->name(); |
|
|
} |
|
|
|
|
|
$result .= $text; |
|
|
|
|
|
$byteSegments = array_merge($byteSegments, $readBytes); |
|
|
} |
|
|
|
|
|
private static function decodeKanjiSegment($bits, |
|
|
&$result, |
|
|
$count) |
|
|
{ |
|
|
|
|
|
if ($count * 13 > $bits->available()) { |
|
|
throw FormatException::getFormatInstance(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$buffer = [0, 2 * $count, 0]; |
|
|
$offset = 0; |
|
|
while ($count > 0) { |
|
|
|
|
|
$twoBytes = $bits->readBits(13); |
|
|
$assembledTwoBytes = (($twoBytes / 0x0C0) << 8) | ($twoBytes % 0x0C0); |
|
|
if ($assembledTwoBytes < 0x01F00) { |
|
|
|
|
|
$assembledTwoBytes += 0x08140; |
|
|
} else { |
|
|
|
|
|
$assembledTwoBytes += 0x0C140; |
|
|
} |
|
|
$buffer[$offset] = ($assembledTwoBytes >> 8); |
|
|
$buffer[$offset + 1] = $assembledTwoBytes; |
|
|
$offset += 2; |
|
|
$count--; |
|
|
} |
|
|
|
|
|
|
|
|
$result .= iconv('shift-jis', 'utf-8', implode($buffer)); |
|
|
} |
|
|
|
|
|
private function DecodedBitStreamParser() |
|
|
{ |
|
|
|
|
|
} |
|
|
} |
|
|
|